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

sev- noreply at scummvm.org
Thu Jun 19 22:25:26 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:
cef1dd6d84 CRUISE: Add text-to-speech (TTS)


Commit: cef1dd6d844e1402dbf2bb2ec2789b3c0f0df84c
    https://github.com/scummvm/scummvm/commit/cef1dd6d844e1402dbf2bb2ec2789b3c0f0df84c
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-06-20T00:25:23+02:00

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

Changed paths:
    engines/cruise/backgroundIncrust.cpp
    engines/cruise/cell.cpp
    engines/cruise/cruise.cpp
    engines/cruise/cruise.h
    engines/cruise/cruise_main.cpp
    engines/cruise/detection.cpp
    engines/cruise/detection.h
    engines/cruise/font.cpp
    engines/cruise/function.cpp
    engines/cruise/menu.cpp
    engines/cruise/metaengine.cpp
    engines/cruise/overlay.cpp


diff --git a/engines/cruise/backgroundIncrust.cpp b/engines/cruise/backgroundIncrust.cpp
index ec8e4a4c871..0760ff462b7 100644
--- a/engines/cruise/backgroundIncrust.cpp
+++ b/engines/cruise/backgroundIncrust.cpp
@@ -24,6 +24,22 @@
 
 namespace Cruise {
 
+static const char *endTexts[] = {
+	"The End",				// English
+	"Fin",					// French
+	"Ende",					// German
+	"El Fin",				// Spanish
+	"Fine"					// Italian
+};
+
+enum EndTextIndex {
+	kEnglish = 0,
+	kFrench = 1,
+	kGerman = 2,
+	kSpanish = 3,
+	kItalian = 4
+};
+
 backgroundIncrustStruct backgroundIncrustHead;
 
 void resetBackgroundIncrustList(backgroundIncrustStruct *pHead) {
@@ -140,6 +156,42 @@ backgroundIncrustStruct *addBackgroundIncrust(int16 overlayIdx,	int16 objectIdx,
 	newElement->ptr = nullptr;
 	Common::strcpy_s(newElement->name, filesDatabase[params.fileIdx].subData.name);
 
+	if (_vm->getLanguage() != Common::RU_RUS && !strcmp(newElement->name, "FIN14.SET")) {	// "The End" (nothing for Russian version)
+		const char *text;
+
+		switch (_vm->getLanguage()) {
+		case Common::EN_GRB:
+		case Common::EN_ANY:
+			text = endTexts[kEnglish];
+			break;
+		case Common::FR_FRA:
+			text = endTexts[kFrench];
+			break;
+		case Common::DE_DEU:
+			text = endTexts[kGerman];
+			break;
+		case Common::ES_ESP:
+			text = endTexts[kSpanish];
+			break;
+		case Common::IT_ITA:
+			text = endTexts[kItalian];
+			break;
+		default:
+			text = endTexts[kEnglish];
+		}
+
+		_vm->sayText(text, Common::TextToSpeechManager::QUEUE);
+	} else if (!strcmp(newElement->name, "CFACSP02.SET")) {	// Title
+		if (_vm->getLanguage() == Common::FR_FRA) {
+			_vm->sayText("Croisi\212re pour un Cadavre", Common::TextToSpeechManager::QUEUE);
+		} else if (_vm->getLanguage() == Common::RU_RUS) {
+			// "Круиз для мертвеца"
+			_vm->sayText("\x8a\xe0\x93\xa8\xa7 \xa4\xab\xef \xac\xa5\xe0\xe2\xa2\xa5\xe6\xa0", Common::TextToSpeechManager::QUEUE);
+		} else {
+			_vm->sayText("Cruise for a Corpse", Common::TextToSpeechManager::QUEUE);
+		}
+	}
+
 	if (filesDatabase[params.fileIdx].subData.resourceType == OBJ_TYPE_SPRITE) {
 		// sprite
 		int width = filesDatabase[params.fileIdx].width;
diff --git a/engines/cruise/cell.cpp b/engines/cruise/cell.cpp
index 5d2990a64d5..82181008dd3 100644
--- a/engines/cruise/cell.cpp
+++ b/engines/cruise/cell.cpp
@@ -22,6 +22,7 @@
 #include "cruise/cruise_main.h"
 #include "common/file.h"
 #include "cruise/cell.h"
+#include "cruise/cruise.h"
 
 namespace Cruise {
 
@@ -159,15 +160,34 @@ void createTextObject(cellStruct *pObject, int overlayIdx, int messageIdx, int x
 	cx->prev = pNewElement;
 
 	ax = getText(messageIdx, overlayIdx);
+	Common::String ttsMessage;
 
 	if (ax) {
 		pNewElement->gfxPtr = renderText(width, ax);
+		ttsMessage = ax;
+		ttsMessage.replace('|', '\n');
 	}
 
-	// WORKAROUND: This is needed for the new dirty rect handling so as to properly refresh the screen
-	// when the copy protection screen is being shown
-	if ((messageIdx == 0) && !strcmp(overlayTable[overlayIdx].overlayName, "XX2"))
-		backgroundChanged[0] = true;
+	if (!strcmp(overlayTable[overlayIdx].overlayName, "XX2")) {
+		// The English DOS and Russian versions automatically skip past the copy protection screen, so don't voice
+		// any of its text
+		if ((_vm->getLanguage() != Common::EN_ANY && _vm->getLanguage() != Common::RU_RUS && _vm->getLanguage() != Common::EN_GRB) || 
+				_vm->getPlatform() != Common::kPlatformDOS) {
+			// Don't voice the "OK" button (index 32) here
+			if (messageIdx != 32) {
+				_vm->sayText(ttsMessage, Common::TextToSpeechManager::QUEUE);
+			}
+		}
+		
+		// WORKAROUND: This is needed for the new dirty rect handling so as to properly refresh the screen
+		// when the copy protection screen is being shown
+		if (messageIdx == 0) 
+			backgroundChanged[0] = true;
+	} else {
+		// Sometimes this text shows up on screen later, so queue it up to be spoken
+		_vm->_toSpeak = ttsMessage;
+		_vm->_previousSaid.clear();
+	}
 }
 
 void removeCell(cellStruct *objPtr, int ovlNumber, int objectIdx, int objType, int backgroundPlane) {
diff --git a/engines/cruise/cruise.cpp b/engines/cruise/cruise.cpp
index 13cfaa42ff1..e09c6578acb 100644
--- a/engines/cruise/cruise.cpp
+++ b/engines/cruise/cruise.cpp
@@ -22,6 +22,7 @@
 #include "common/file.h"
 #include "common/debug-channels.h"
 #include "common/textconsole.h"
+#include "common/system.h"
 
 #include "engines/util.h"
 
@@ -55,6 +56,9 @@ CruiseEngine::CruiseEngine(OSystem * syst, const CRUISEGameDescription *gameDesc
 	_polyStructs = nullptr;
 	_polyStruct = nullptr;
 
+	_mouseButtonDown = false;
+	_menuJustOpened = false;
+
 	// Setup mixer
 	syncSoundSettings();
 }
@@ -86,6 +90,18 @@ Common::Error CruiseEngine::run() {
 		return Common::kUnknownError;	// for compilers that don't support NORETURN
 	}
 
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+
+		if (getLanguage() == Common::RU_RUS) {
+			_ttsTextEncoding = Common::CodePage::kDos866;
+		} else {
+			_ttsTextEncoding = Common::CodePage::kDos850;
+		}
+	}
+
 	initialize();
 
 	Cruise::changeCursor(Cruise::CURSOR_NORMAL);
@@ -194,11 +210,38 @@ void CruiseEngine::pauseEngine(bool pause) {
 		processAnimation();
 		flipScreen();
 		changeCursor(_savedCursor);
+
+		_vm->stopTextToSpeech();
 	}
 
 	gfxModuleData_addDirtyRect(Common::Rect(64, 100, 256, 117));
 }
 
+void CruiseEngine::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 the cursor stays on a menu item. 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 != nullptr && ConfMan.getBool("tts_enabled") && _previousSaid != text) {
+		ttsMan->say(text, action, _ttsTextEncoding);
+		_previousSaid = text;
+	}
+}
+
+void CruiseEngine::sayQueuedText(Common::TextToSpeechManager::Action action) {
+	sayText(_toSpeak, action);
+	_toSpeak.clear();
+}
+
+void CruiseEngine::stopTextToSpeech() {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+		ttsMan->stop();
+		_previousSaid.clear();
+	}
+}
+
 Common::Error CruiseEngine::loadGameState(int slot) {
 	return loadSavegameData(slot);
 }
diff --git a/engines/cruise/cruise.h b/engines/cruise/cruise.h
index ae18dbe1b23..d85016f5fb8 100644
--- a/engines/cruise/cruise.h
+++ b/engines/cruise/cruise.h
@@ -25,6 +25,7 @@
 #include "common/scummsys.h"
 #include "common/util.h"
 #include "common/random.h"
+#include "common/text-to-speech.h"
 
 #include "engines/engine.h"
 
@@ -78,6 +79,8 @@ private:
 	bool _speedFlag;
 	PauseToken _gamePauseToken;
 
+	Common::CodePage _ttsTextEncoding;
+
 	void initialize();
 	void deinitialize();
 	bool loadLanguageStrings();
@@ -104,6 +107,9 @@ public:
 	PCSound &sound() { return *_sound; }
 	virtual void pauseEngine(bool pause);
 	const char *langString(LangStringId langId) { return _langStrings[(int)langId].c_str(); }
+	void sayText(const Common::String &text, Common::TextToSpeechManager::Action action);
+	void sayQueuedText(Common::TextToSpeechManager::Action action);
+	void stopTextToSpeech();
 
 	static const char *getSavegameFile(int saveGameIdx);
 	Common::Error loadGameState(int slot) override;
@@ -141,6 +147,11 @@ public:
 	Common::Array<CtStruct> *_polyStruct;
 
 	Common::File _PAL_file;
+
+	Common::String _toSpeak;
+	Common::String _previousSaid;
+	bool _mouseButtonDown;
+	bool _menuJustOpened;
 };
 
 extern CruiseEngine *_vm;
diff --git a/engines/cruise/cruise_main.cpp b/engines/cruise/cruise_main.cpp
index 4c8a25c56f1..78d0523b447 100644
--- a/engines/cruise/cruise_main.cpp
+++ b/engines/cruise/cruise_main.cpp
@@ -712,7 +712,16 @@ int findObject(int mouseX, int mouseY, int *outObjOvl, int *outObjIdx) {
 								*outObjOvl = linkedObjOvl;
 								*outObjIdx = linkedObjIdx;
 
+								// "Ok" button on the copy protection screen
+								if (!strcmp(overlayTable[currentObject->overlay].overlayName, "XX2") && 
+											(currentObject->idx == 1 || currentObject->idx == 0)) {
+									_vm->sayText("OK", Common::TextToSpeechManager::INTERRUPT);
+								}
+
 								return (currentObject->type);
+							} else if (!strcmp(overlayTable[currentObject->overlay].overlayName, "XX2") && 
+											(currentObject->idx == 1 || currentObject->idx == 0)) {
+								_vm->_previousSaid.clear();
 							}
 						} else {
 							// int numBitPlanes = filesDatabase[j].resType;
@@ -810,6 +819,8 @@ void buildInventory(int X, int Y) {
 	if (numObjectInInventory == 0) {
 		freeMenu(menuTable[1]);
 		menuTable[1] = nullptr;
+	} else {
+		_vm->sayText(_vm->langString(ID_INVENTORY), Common::TextToSpeechManager::INTERRUPT);
 	}
 }
 
@@ -835,6 +846,7 @@ menuElementSubStruct *getSelectedEntryInMenu(menuStruct *pMenu) {
 			currentMenuElementX = pMenuElement->x;
 			currentMenuElementY = pMenuElement->y;
 			currentMenuElement = pMenuElement;
+			_vm->stopTextToSpeech();
 
 			return pMenuElement->ptrSub;
 		}
@@ -957,6 +969,8 @@ bool findRelation(int objOvl, int objIdx, int x, int y) {
 
 	getSingleObjectParam(objOvl, objIdx, 5, &objectState);
 
+	Common::String ttsMessage;
+
 	for (int j = 1; j < numOfLoadedOverlay; j++) {
 		if (overlayTable[j].alreadyLoaded) {
 			int idHeader = overlayTable[j].ovlData->numMsgRelHeader;
@@ -1009,6 +1023,7 @@ bool findRelation(int objOvl, int objIdx, int x, int y) {
 							const char *ptrName = getObjectName(ptrHead->obj1Number, ovl3->arrayNameObj);
 
 							menuTable[0] = createMenu(x, y, ptrName);
+							ttsMessage = ptrName;
 							first = false;
 						}
 					}
@@ -1037,6 +1052,10 @@ bool findRelation(int objOvl, int objIdx, int x, int y) {
 		}
 	}
 
+	if (found) {
+		_vm->sayText(ttsMessage, Common::TextToSpeechManager::INTERRUPT);
+	}
+
 	return found;
 }
 
@@ -1191,6 +1210,8 @@ void callSubRelation(menuElementSubStruct *pMenuElement, int nOvl, int nObj) {
 							userEnabled = 0;
 							freezeCell(&cellHead, ovlIdx, pHeader->id, 5, -1, 0, 9998);
 						}
+					} else {
+						_vm->sayQueuedText(Common::TextToSpeechManager::QUEUE);
 					}
 				}
 			}
@@ -1339,6 +1360,8 @@ void callRelation(menuElementSubStruct *pMenuElement, int nObj2) {
 						userEnabled = 0;
 						freezeCell(&cellHead, ovlIdx, pHeader->id, 5, -1, 0, 9998);
 					}
+				} else {
+					_vm->sayQueuedText(Common::TextToSpeechManager::QUEUE);
 				}
 			}
 		} else {
@@ -1451,8 +1474,10 @@ int CruiseEngine::processInput() {
 
 	if (userWait) {
 		// Check for left mouse button click or Space to end user waiting
-		if ((action == kActionEndUserWaiting) || (button == CRS_MB_LEFT))
+		if ((action == kActionEndUserWaiting) || (button == CRS_MB_LEFT)) {
 			userWait = false;
+			stopTextToSpeech();
+		}
 
 		action = kActionNone;
 		return 0;
@@ -1493,6 +1518,8 @@ int CruiseEngine::processInput() {
 
 			if (menuTable[0]) {
 				if (dialogFound) {
+					sayText(menuTable[0]->stringPtr, Common::TextToSpeechManager::INTERRUPT);
+
 					currentActiveMenu = 0;
 				} else {
 					freeMenu(menuTable[0]);
@@ -1621,6 +1648,11 @@ int CruiseEngine::processInput() {
 							Common::strcat_s(text, ":");
 							Common::strcat_s(text, currentMenuElement->string);
 							linkedMsgList = renderText(320, (const char *)text);
+
+							Common::String ttsMessage = text;
+							Common::replace(ttsMessage, ":", ": ");
+							sayText(ttsMessage, Common::TextToSpeechManager::INTERRUPT);
+
 							changeCursor(CURSOR_CROSS);
 						}
 					}
@@ -1686,6 +1718,7 @@ bool manageEvents() {
 		switch (event.type) {
 		case Common::EVENT_LBUTTONDOWN:
 			currentMouseButton |= CRS_MB_LEFT;
+			_vm->_mouseButtonDown = true;
 			break;
 		case Common::EVENT_LBUTTONUP:
 			currentMouseButton &= ~CRS_MB_LEFT;
@@ -1693,6 +1726,7 @@ bool manageEvents() {
 			break;
 		case Common::EVENT_RBUTTONDOWN:
 			currentMouseButton |= CRS_MB_RIGHT;
+			_vm->_mouseButtonDown = true;
 			break;
 		case Common::EVENT_RBUTTONUP:
 			currentMouseButton &= ~CRS_MB_RIGHT;
@@ -1942,6 +1976,7 @@ void CruiseEngine::mainLoop() {
 				if (isAnimFinished(narratorOvl, narratorIdx, &actorHead, ATP_MOUSE)) {
 					if (autoMsg != -1) {
 						freezeCell(&cellHead, autoOvl, autoMsg, 5, -1, 9998, 0);
+						sayQueuedText(Common::TextToSpeechManager::QUEUE);
 
 						char* pText = getText(autoMsg, autoOvl);
 
diff --git a/engines/cruise/detection.cpp b/engines/cruise/detection.cpp
index ef6be115897..97524ad319a 100644
--- a/engines/cruise/detection.cpp
+++ b/engines/cruise/detection.cpp
@@ -47,7 +47,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::EN_GRB,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -58,7 +58,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::FR_FRA,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -69,7 +69,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -80,7 +80,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::FR_FRA,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -91,7 +91,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::DE_DEU,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -102,7 +102,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::DE_DEU,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -113,7 +113,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::IT_ITA,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -124,7 +124,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::ES_ESP,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Fanmade translation by old-games.ru
@@ -135,7 +135,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::RU_RUS,
 			Common::kPlatformDOS,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{
@@ -146,7 +146,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::DE_DEU,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga English US GOLD edition.
@@ -157,7 +157,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga English US GOLD edition (Delphine Collection).
@@ -168,7 +168,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga Italian US GOLD edition.
@@ -179,7 +179,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::IT_ITA,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga Spanish edition.
@@ -190,7 +190,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::ES_ESP,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga French edition.
@@ -201,7 +201,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::FR_FRA,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga French edition (alternate).
@@ -212,7 +212,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::FR_FRA,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // Amiga Italian (Fanmade translation 1.0).
@@ -223,7 +223,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::IT_ITA,
 			Common::kPlatformAmiga,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // AtariST English KixxXL edition.
@@ -234,7 +234,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::EN_ANY,
 			Common::kPlatformAtariST,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{ // AtariST French edition. Bugreport #12824
@@ -245,7 +245,7 @@ static const CRUISEGameDescription gameDescriptions[] = {
 			Common::FR_FRA,
 			Common::kPlatformAtariST,
 			ADGF_NO_FLAGS,
-			GUIO0()
+			GUIO1(GAMEOPTION_TTS)
 		},
 	},
 	{AD_TABLE_END_MARKER}
diff --git a/engines/cruise/detection.h b/engines/cruise/detection.h
index 3e82dc2eeb1..f45889a9100 100644
--- a/engines/cruise/detection.h
+++ b/engines/cruise/detection.h
@@ -30,6 +30,8 @@ struct CRUISEGameDescription {
 	ADGameDescription desc;
 };
 
+#define GAMEOPTION_TTS                    GUIO_GAMEOPTIONS1
+
 } // End of namespace Cruise
 
 #endif // CRUISE_DETECTION_H
diff --git a/engines/cruise/font.cpp b/engines/cruise/font.cpp
index 5c8fdc9542b..cd776e2e40a 100644
--- a/engines/cruise/font.cpp
+++ b/engines/cruise/font.cpp
@@ -270,6 +270,8 @@ void drawString(int32 x, int32 y, const char *string, uint8 *buffer, uint8 fontC
 	// Draw the message
 	drawMessage(s, x, y, rightBorder_X - x, fontColor, buffer);
 
+	_vm->sayText(string, Common::TextToSpeechManager::INTERRUPT);
+
 	// Free the data
 	delete s->imagePtr;
 	free(s);
diff --git a/engines/cruise/function.cpp b/engines/cruise/function.cpp
index 48d6d087d57..ba0f36978e0 100644
--- a/engines/cruise/function.cpp
+++ b/engines/cruise/function.cpp
@@ -713,6 +713,15 @@ int16 Op_GetMouseButton() {
 
 	getMouseStatus(&dummy, &mouseX, &mouseButton, &mouseY);
 
+	// Stop TTS when progressing a dialog or switching screens with user input
+	// _mouseButtonDown is used to prevent cases where the mouse input in this function is repeated several times
+	// after a click, which sometimes immediately ends TTS for a message that just appeared on screen
+	if ((mouseButton == 1 || mouseButton == 2) && !userEnabled && _vm->_mouseButtonDown && 
+			strcmp(overlayTable[currentScriptPtr->overlayNumber].overlayName, "SHIP")) {
+		_vm->stopTextToSpeech();
+		_vm->_mouseButtonDown = false;
+	}
+
 	return mouseButton;
 }
 
@@ -819,6 +828,13 @@ int16 Op_AddMessage() {
 	}
 
 	createTextObject(&cellHead, overlayIdx, var_8, var_6, var_4, var_2, color, masterScreen, currentScriptPtr->overlayNumber, currentScriptPtr->scriptNumber);
+	
+	// Interrupt for location names on the map to avoid a long queue if the user clicks a lot of locations
+	if (!strcmp(overlayTable[currentScriptPtr->overlayNumber].overlayName, "SHIP")) {
+		_vm->sayQueuedText(Common::TextToSpeechManager::INTERRUPT);
+	} else {
+		_vm->sayQueuedText(Common::TextToSpeechManager::QUEUE);
+	}
 
 	return 0;
 }
diff --git a/engines/cruise/menu.cpp b/engines/cruise/menu.cpp
index 45acc239a56..9c8260f5065 100644
--- a/engines/cruise/menu.cpp
+++ b/engines/cruise/menu.cpp
@@ -45,6 +45,7 @@ menuStruct *createMenu(int X, int Y, const char *menuName) {
 	entry->numElements = 0;
 	entry->ptrNextElement = nullptr;
 	entry->gfx = renderText(160, menuName);
+	_vm->_menuJustOpened = true;
 
 	return entry;
 }
@@ -124,6 +125,8 @@ void addSelectableMenuEntry(int ovlIdx, int headerIdx, menuStruct *pMenu, int pa
 }
 
 void updateMenuMouse(int mouseX, int mouseY, menuStruct *pMenu) {
+	bool menuItemSelected = false;
+
 	if (pMenu) {
 		if (pMenu->gfx) {
 			int height = pMenu->gfx->height;	// rustine
@@ -138,6 +141,18 @@ void updateMenuMouse(int mouseX, int mouseY, menuStruct *pMenu) {
 						if ((mouseY > pCurrentEntry->y) && ((pCurrentEntry->y + height) >= mouseY)) {
 							var_2 = 1;
 							pCurrentEntry->selected = true;
+
+							// Queue up the first selected item after opening the menu
+							// This allows the name of the menu to be always voiced, in cases where something
+							// is selected the instant the menu opens
+							if (_vm->_menuJustOpened) {
+								_vm->sayText(pCurrentEntry->string, Common::TextToSpeechManager::QUEUE);
+								_vm->_menuJustOpened = false;
+							} else {
+								_vm->sayText(pCurrentEntry->string, Common::TextToSpeechManager::INTERRUPT);
+							}
+
+							menuItemSelected = true;
 						}
 					}
 				}
@@ -146,6 +161,10 @@ void updateMenuMouse(int mouseX, int mouseY, menuStruct *pMenu) {
 			}
 		}
 	}
+
+	if (!menuItemSelected) {
+		_vm->_previousSaid.clear();
+	}
 }
 
 bool manageEvents();
@@ -248,6 +267,7 @@ int playerMenu(int menuX, int menuY) {
 
 		menuTable[0] = createMenu(menuX, menuY, _vm->langString(ID_PLAYER_MENU));
 		assert(menuTable[0]);
+		_vm->sayText(_vm->langString(ID_PLAYER_MENU), Common::TextToSpeechManager::INTERRUPT);
 
 		//addSelectableMenuEntry(0, 3, menuTable[0], 1, -1, "Save game disk");
 		if (userEnabled) {
diff --git a/engines/cruise/metaengine.cpp b/engines/cruise/metaengine.cpp
index bc05fc31738..8a27045dc72 100644
--- a/engines/cruise/metaengine.cpp
+++ b/engines/cruise/metaengine.cpp
@@ -36,6 +36,26 @@
 
 namespace Cruise {
 
+#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
+
 const char *CruiseEngine::getGameId() const {
 	return _gameDescription->desc.gameId;
 }
@@ -55,6 +75,12 @@ public:
 		return "cruise";
 	}
 
+#ifdef USE_TTS
+	const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+		return Cruise::optionsList;
+	}
+#endif
+
 	bool hasFeature(MetaEngineFeature f) const override;
 	int getMaximumSaveSlot() const override { return 99; }
 
diff --git a/engines/cruise/overlay.cpp b/engines/cruise/overlay.cpp
index 016b6d7c26c..27889958401 100644
--- a/engines/cruise/overlay.cpp
+++ b/engines/cruise/overlay.cpp
@@ -143,6 +143,10 @@ int loadOverlay(const char *scriptName) {
 		scriptNotLoadedBefore = true;
 	}
 
+	if (!strcmp(scriptName, "LOGO")) {	// Logo when game is first booted up (or restarted)
+		_vm->sayText("Delphine Software\nCinematique", Common::TextToSpeechManager::QUEUE);
+	}
+
 	if (overlayTable[scriptIdx].alreadyLoaded) {
 		return (scriptIdx);
 	}




More information about the Scummvm-git-logs mailing list