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

sev- noreply at scummvm.org
Mon Aug 18 10:27:23 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:
a78a3102b8 AGI: Add text-to-speech (TTS)


Commit: a78a3102b84eeac1465089fc635650e7b33bb526
    https://github.com/scummvm/scummvm/commit/a78a3102b84eeac1465089fc635650e7b33bb526
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-08-18T12:27:20+02:00

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

Changed paths:
    engines/agi/agi.cpp
    engines/agi/agi.h
    engines/agi/cycle.cpp
    engines/agi/detection.h
    engines/agi/detection_tables.h
    engines/agi/inv.cpp
    engines/agi/menu.cpp
    engines/agi/metaengine.cpp
    engines/agi/op_cmd.cpp
    engines/agi/op_test.cpp
    engines/agi/saveload.cpp
    engines/agi/systemui.cpp
    engines/agi/text.cpp
    engines/agi/text.h


diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 7280068fa63..95d4ad4082e 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -470,6 +470,13 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas
 
 	_artificialDelayCurrentRoom = 0;
 	_artificialDelayCurrentPicture = 0;
+
+#ifdef USE_TTS
+	_previousDisplayRow = -1;
+	_queueNextText = false;
+	_voiceClock = true;
+	_replaceDisplayNewlines = true;
+#endif
 }
 
 void AgiEngine::initialize() {
@@ -547,6 +554,26 @@ void AgiEngine::initialize() {
 	
 	// finally set up actual VM opcodes, because we should now have figured out the right AGI version
 	setupOpCodes(getVersion());
+
+#ifdef USE_TTS
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan) {
+		ttsMan->enable(ConfMan.getBool("tts_enabled"));
+		ttsMan->setLanguage(ConfMan.get("language"));
+	}
+
+	switch (getLanguage()) {
+	case Common::HE_ISR:
+		_ttsEncoding = Common::CodePage::kWindows1255;
+		break;
+	case Common::RU_RUS:
+		_ttsEncoding = Common::CodePage::kDos866;
+		break;
+	default:
+		_ttsEncoding = Common::CodePage::kDos850;
+		break;
+	}
+#endif
 }
 
 bool AgiEngine::promptIsEnabled() {
@@ -615,6 +642,41 @@ void AgiEngine::syncSoundSettings() {
 	applyVolumeToMixer();
 }
 
+#ifdef USE_TTS
+
+void AgiEngine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action, bool checkPreviousSaid) {
+	if (text.empty()) {
+		return;
+	}
+
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ConfMan.getBool("tts_enabled") && (!checkPreviousSaid || _previousSaid != text)) {
+		Common::String ttsMessage = text;
+
+		if (_replaceDisplayNewlines) {
+			ttsMessage.replace('\n', ' ');
+		}
+		
+		ttsMessage.replace('<', ' ');
+		ttsMessage.replace('=', ' ');
+		ttsMan->say(ttsMessage, action, _ttsEncoding);
+		_previousSaid = text;
+	}
+}
+
+void AgiEngine::stopTextToSpeech(bool clearPreviousSaid) {
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+		ttsMan->stop();
+
+		if (clearPreviousSaid) {
+			_previousSaid.clear();
+		}
+	}
+}
+
+#endif
+
 // WORKAROUND:
 // Sometimes Sierra printed some text on the screen and did a room change immediately afterwards expecting the
 // interpreter to load the data for a bit of time. This of course doesn't happen in our AGI, so we try to
@@ -734,6 +796,20 @@ void AgiEngine::artificialDelayTrigger_NewRoom(int16 newRoomNr) {
 		}
 	}
 
+#ifdef USE_TTS
+	// Delay room changes until TTS is done speaking, which helps TTS keep up with the text on screen
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while (ttsMan && ttsMan->isSpeaking()) {
+		int key = doPollKeyboard();
+
+		if (key != 0) {
+			break;
+		}
+
+		wait(10);
+	}
+#endif
+
 	_artificialDelayCurrentRoom = newRoomNr;
 }
 
diff --git a/engines/agi/agi.h b/engines/agi/agi.h
index ee1f87a24bc..7a8e7ddb151 100644
--- a/engines/agi/agi.h
+++ b/engines/agi/agi.h
@@ -32,6 +32,7 @@
 #include "common/stack.h"
 #include "common/str.h"
 #include "common/system.h"
+#include "common/text-to-speech.h"
 
 #include "engines/engine.h"
 
@@ -755,6 +756,16 @@ public:
 
 	Common::Stack<ImageStackElement> _imageStack;
 
+#ifdef USE_TTS
+	int16 _previousDisplayRow;
+	Common::String _combinedText;
+	Common::String _previousSaid;
+	bool _queueNextText;
+	bool _voiceClock;
+	bool _replaceDisplayNewlines;
+	Common::CodePage _ttsEncoding;
+#endif
+
 	void clearImageStack() override;
 	void recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
 	                          int16 p4, int16 p5, int16 p6, int16 p7) override;
@@ -786,6 +797,12 @@ private:
 public:
 	void syncSoundSettings() override;
 
+#ifdef USE_TTS
+	void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::QUEUE, 
+				 bool checkPreviousSaid = true);
+	void stopTextToSpeech(bool clearPreviousSaid = true);
+#endif
+
 public:
 	void decrypt(uint8 *mem, int len);
 	uint16 processAGIEvents();
diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp
index 3f49e6dcc68..81421b47a43 100644
--- a/engines/agi/cycle.cpp
+++ b/engines/agi/cycle.cpp
@@ -195,7 +195,7 @@ void AgiEngine::interpretCycle() {
 	screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION);
 
 	if (getVar(VM_VAR_SCORE) != oldScore || getFlag(VM_FLAG_SOUND_ON) != oldSound)
-		_game._vm->_text->statusDraw();
+		_game._vm->_text->statusDraw(getVar(VM_VAR_SCORE) != oldScore, getFlag(VM_FLAG_SOUND_ON) != oldSound);
 
 	setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
 	setVar(VM_VAR_BORDER_CODE, 0);
diff --git a/engines/agi/detection.h b/engines/agi/detection.h
index d9c26bdbd36..4afab227a08 100644
--- a/engines/agi/detection.h
+++ b/engines/agi/detection.h
@@ -46,6 +46,7 @@ struct AGIGameDescription {
 #define GAMEOPTION_COPY_PROTECTION             GUIO_GAMEOPTIONS7
 #define GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE GUIO_GAMEOPTIONS8
 #define GAMEOPTION_PCJR_SN76496_16BIT          GUIO_GAMEOPTIONS9
+#define GAMEOPTION_TTS                         GUIO_GAMEOPTIONS10
 
 } // End of namespace Agi
 
diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h
index 2ccd5d3f81c..05d88d21eba 100644
--- a/engines/agi/detection_tables.h
+++ b/engines/agi/detection_tables.h
@@ -23,15 +23,15 @@
 
 namespace Agi {
 
-#define GAMEOPTIONS_DEFAULT                   GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW)
-#define GAMEOPTIONS_DEFAULT_CP                GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_COPY_PROTECTION)
-#define GAMEOPTIONS_AMIGA                     GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW)
-#define GAMEOPTIONS_AMIGA_CP                  GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_COPY_PROTECTION)
-#define GAMEOPTIONS_APPLE2GS                  GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW, GAMEOPTION_APPLE2GS_ADD_SPEED_MENU)
-#define GAMEOPTIONS_APPLE2GS_CP               GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW, GAMEOPTION_APPLE2GS_ADD_SPEED_MENU, GAMEOPTION_COPY_PROTECTION)
-#define GAMEOPTIONS_VGA                       GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GUIO_RENDERVGA)
-#define GAMEOPTIONS_FANMADE_MOUSE             GUIO5(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_PCJR_SN76496_16BIT)
-#define GAMEOPTIONS_FANMADE_MOUSE_VGA         GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_PCJR_SN76496_16BIT,GUIO_RENDERVGA)
+#define GAMEOPTIONS_DEFAULT                   GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_TTS)
+#define GAMEOPTIONS_DEFAULT_CP                GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_COPY_PROTECTION,GAMEOPTION_TTS)
+#define GAMEOPTIONS_AMIGA                     GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_TTS)
+#define GAMEOPTIONS_AMIGA_CP                  GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_COPY_PROTECTION,GAMEOPTION_TTS)
+#define GAMEOPTIONS_APPLE2GS                  GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW, GAMEOPTION_APPLE2GS_ADD_SPEED_MENU,GAMEOPTION_TTS)
+#define GAMEOPTIONS_APPLE2GS_CP               GUIO8(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW, GAMEOPTION_APPLE2GS_ADD_SPEED_MENU, GAMEOPTION_COPY_PROTECTION,GAMEOPTION_TTS)
+#define GAMEOPTIONS_VGA                       GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_MOUSE,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GUIO_RENDERVGA,GAMEOPTION_TTS)
+#define GAMEOPTIONS_FANMADE_MOUSE             GUIO6(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_PCJR_SN76496_16BIT,GAMEOPTION_TTS)
+#define GAMEOPTIONS_FANMADE_MOUSE_VGA         GUIO7(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_ENABLE_PREDICTIVE_FOR_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW,GAMEOPTION_PCJR_SN76496_16BIT,GUIO_RENDERVGA,GAMEOPTION_TTS)
 
 #define GAME_LVFPN(id,extra,fname,md5,size,lang,ver,features,gid,platform,interp,guioptions) { \
 		{ \
diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp
index bc336c0309f..c1cbc997a27 100644
--- a/engines/agi/inv.cpp
+++ b/engines/agi/inv.cpp
@@ -109,6 +109,9 @@ void InventoryMgr::drawAll() {
 
 	_text->charPos_Set(0, 11);
 	_text->displayText(_systemUI->getInventoryTextYouAreCarrying());
+#ifdef USE_TTS
+	_vm->sayText(_systemUI->getInventoryTextYouAreCarrying(), Common::TextToSpeechManager::INTERRUPT, false);
+#endif
 
 	for (int16 inventoryNr = 0; inventoryNr < inventoryCount; inventoryNr++) {
 		drawItem(inventoryNr);
@@ -118,8 +121,23 @@ void InventoryMgr::drawAll() {
 void InventoryMgr::drawItem(int16 itemNr) {
 	if (itemNr == _activeItemNr) {
 		_text->charAttrib_Set(15, 0);
+
+#ifdef USE_TTS
+		if (_vm->_queueNextText) {
+			_vm->sayText(_array[itemNr].name, Common::TextToSpeechManager::QUEUE, false);
+			_vm->_queueNextText = false;
+		} else {
+			_vm->sayText(_array[itemNr].name, Common::TextToSpeechManager::INTERRUPT, false);
+		}
+#endif
 	} else {
 		_text->charAttrib_Set(0, 15);
+
+#ifdef USE_TTS
+		if (_activeItemNr == -1) {
+			_vm->sayText(_array[itemNr].name, Common::TextToSpeechManager::QUEUE, false);
+		}
+#endif
 	}
 
 	_text->charPos_Set(_array[itemNr].row, _array[itemNr].column);
@@ -143,15 +161,24 @@ void InventoryMgr::show() {
 		_activeItemNr = -1; // so that none is shown as active
 	}
 
+#ifdef USE_TTS
+	_vm->_queueNextText = true;
+#endif
 	drawAll();
 
 	_text->charAttrib_Set(0, 15);
 	if (selectItems) {
 		_text->charPos_Set(24, 2);
 		_text->displayText(_systemUI->getInventoryTextSelectItems());
+#ifdef USE_TTS
+		_vm->sayText(_systemUI->getInventoryTextSelectItems());
+#endif
 	} else {
 		_text->charPos_Set(24, 4);
 		_text->displayText(_systemUI->getInventoryTextReturnToGame());
+#ifdef USE_TTS
+		_vm->sayText(_systemUI->getInventoryTextReturnToGame());
+#endif
 	}
 
 	if (selectItems) {
@@ -168,6 +195,9 @@ void InventoryMgr::show() {
 			// nothing was selected
 			_vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, 0xff);
 		}
+#ifdef USE_TTS
+		_vm->stopTextToSpeech();
+#endif
 
 	} else {
 		// no selection is supposed to be possible, just wait for key and exit
diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp
index 17f229abb75..922c2b86dd4 100644
--- a/engines/agi/menu.cpp
+++ b/engines/agi/menu.cpp
@@ -355,6 +355,9 @@ void GfxMenu::execute() {
 
 	if (_drawnMenuNr >= 0) {
 		if (viaKeyboard) {
+#ifdef USE_TTS
+			_vm->_queueNextText = true;
+#endif
 			drawMenu(_drawnMenuNr, _array[_drawnMenuNr]->selectedItemNr);
 		}
 		if (viaMouse) {
@@ -410,6 +413,9 @@ void GfxMenu::drawMenuName(int16 menuNr, bool inverted) {
 	if (!inverted) {
 		_text->charAttrib_Set(0, _text->calculateTextBackground(15));
 	} else {
+#ifdef USE_TTS
+		_vm->sayText(menuEntry->text.c_str(), Common::TextToSpeechManager::INTERRUPT, false);
+#endif
 		_text->charAttrib_Set(15, _text->calculateTextBackground(0));
 	}
 
@@ -428,6 +434,15 @@ void GfxMenu::drawItemName(int16 itemNr, bool inverted) {
 	if (!inverted) {
 		_text->charAttrib_Set(0, _text->calculateTextBackground(15));
 	} else {
+#ifdef USE_TTS
+		if (_vm->_queueNextText) {
+			_vm->sayText(itemEntry->text.c_str(), Common::TextToSpeechManager::QUEUE, false);
+			_vm->_queueNextText = false;
+		} else {
+			_vm->sayText(itemEntry->text.c_str(), Common::TextToSpeechManager::INTERRUPT, false);
+		}
+#endif
+		
 		_text->charAttrib_Set(15, _text->calculateTextBackground(0));
 	}
 
@@ -549,6 +564,9 @@ void GfxMenu::keyPress(uint16 newKey) {
 		}
 
 		if (newMenuNr != _drawnMenuNr) {
+#ifdef USE_TTS
+			_vm->_queueNextText = true;
+#endif
 			removeActiveMenu(_drawnMenuNr);
 			_drawnMenuNr = newMenuNr;
 			drawMenu(_drawnMenuNr, _array[_drawnMenuNr]->selectedItemNr);
diff --git a/engines/agi/metaengine.cpp b/engines/agi/metaengine.cpp
index e0651f9522a..deecb1c151e 100644
--- a/engines/agi/metaengine.cpp
+++ b/engines/agi/metaengine.cpp
@@ -213,6 +213,20 @@ static const ADExtraGuiOptionsMap optionsList[] = {
 		}
 	},
 
+#ifdef USE_TTS
+	{
+		GAMEOPTION_TTS,
+		{
+			_s("Enable Text to Speech"),
+			_s("Use TTS to read text in the game (if TTS is available)"),
+			"tts_enabled",
+			false,
+			0,
+			0
+		}
+	},
+#endif
+
 	AD_EXTRA_GUI_OPTIONS_TERMINATOR
 };
 
diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp
index 2f918270495..464e19709f7 100644
--- a/engines/agi/op_cmd.cpp
+++ b/engines/agi/op_cmd.cpp
@@ -668,12 +668,29 @@ void cmdCloseDialogue(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 }
 
 void cmdCloseWindow(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
+#ifdef USE_TTS
+	// Delay closing the text window until TTS is finished
+	Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+	while (ttsMan && ttsMan->isSpeaking()) {
+		int key = vm->doPollKeyboard();
+
+		if (key != 0) {
+			break;
+		}
+
+		vm->wait(10);
+	}
+#endif
+
 	vm->_text->closeWindow();
 }
 
 void cmdStatusLineOn(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	TextMgr *text = vm->_text;
 
+#ifdef USE_TTS
+	vm->_voiceClock = true;
+#endif
 	text->statusEnable();
 	text->statusDraw();
 }
@@ -1826,6 +1843,9 @@ void cmdTextScreen(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 void cmdGraphics(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 	debugC(4, kDebugLevelScripts, "switching to graphics mode");
 
+#ifdef USE_TTS
+	vm->stopTextToSpeech();
+#endif
 	vm->redrawScreen();
 }
 
@@ -2012,6 +2032,10 @@ void cmdGetString(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 		leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure
 
 		textMgr->displayText(leadInTextPtr);
+#ifdef USE_TTS
+		vm->sayText(vm->_combinedText, Common::TextToSpeechManager::INTERRUPT);
+		vm->_combinedText.clear();
+#endif
 	}
 
 	vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING);
@@ -2049,6 +2073,10 @@ void cmdGetNum(AgiGame *state, AgiEngine *vm, uint8 *parameter) {
 		leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure
 
 		textMgr->displayText(leadInTextPtr);
+#ifdef USE_TTS
+		vm->sayText(vm->_combinedText, Common::TextToSpeechManager::INTERRUPT);
+		vm->_combinedText.clear();
+#endif
 	}
 
 	textMgr->inputEditOff();
@@ -2436,6 +2464,13 @@ int AgiEngine::runLogic(int16 logicNr) {
 			debugC(2, kDebugLevelScripts, "%sreturn() // Logic %d", st, logicNr);
 			debugC(2, kDebugLevelScripts, "=================");
 
+#ifdef USE_TTS
+		sayText(_combinedText, Common::TextToSpeechManager::QUEUE, true);
+		_replaceDisplayNewlines = true;
+		_combinedText.clear();
+		_previousDisplayRow = -1;
+#endif
+
 //			if (vm->getVersion() < 0x2000) {
 //				if (logic_index < state->max_logics) {
 //					n = state->logic_list[++logic_index];
diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp
index f290f22cb14..bc843b58b1c 100644
--- a/engines/agi/op_test.cpp
+++ b/engines/agi/op_test.cpp
@@ -122,6 +122,9 @@ void condHaveKey(AgiGame *state, AgiEngine *vm, uint8 *p) {
 	// Only check for key when there is not already one set by scripts
 	if (vm->getVar(VM_VAR_KEY)) {
 		state->testResult = true;
+#ifdef USE_TTS
+		vm->stopTextToSpeech(false);
+#endif
 		return;
 	}
 	// we are not really an inner loop, but we stop processAGIEvents() from doing regular cycle work by setting it up
@@ -132,6 +135,9 @@ void condHaveKey(AgiGame *state, AgiEngine *vm, uint8 *p) {
 		debugC(5, kDebugLevelInput, "keypress = %02x", key);
 		vm->setVar(VM_VAR_KEY, key);
 		state->testResult = true;
+#ifdef USE_TTS
+		vm->stopTextToSpeech(false);
+#endif
 		return;
 	}
 	state->testResult = false;
@@ -278,6 +284,11 @@ bool AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {
 }
 
 bool AgiEngine::testController(uint8 cont) {
+#ifdef USE_TTS
+	if (_game.controllerOccurred[cont]) {
+		stopTextToSpeech(false);
+	}
+#endif
 	return _game.controllerOccurred[cont];
 }
 
diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp
index d5f8a2b1dc9..88f5b468967 100644
--- a/engines/agi/saveload.cpp
+++ b/engines/agi/saveload.cpp
@@ -746,7 +746,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
 	_sprites->drawAllSpriteLists();
 	_picture->showPictureWithTransition();
 	_game.pictureShown = true;
-	_text->statusDraw();
+	_text->statusDraw(true, true);
 	_text->promptRedraw();
 
 	// copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen
diff --git a/engines/agi/systemui.cpp b/engines/agi/systemui.cpp
index 2b981e21a43..40cd61af338 100644
--- a/engines/agi/systemui.cpp
+++ b/engines/agi/systemui.cpp
@@ -260,6 +260,9 @@ bool SystemUI::askForCommand(Common::String &commandText) {
 	bool previousEditState = _text->inputGetEditStatus();
 	byte previousEditCursor = _text->inputGetCursorChar();
 
+#ifdef USE_TTS
+	_vm->stopTextToSpeech();
+#endif
 	_text->drawMessageBox(_textEnterCommand, 0, 36, true);
 
 	_text->inputEditOn();
@@ -284,7 +287,7 @@ bool SystemUI::askForCommand(Common::String &commandText) {
 		_text->inputEditOff();
 	}
 
-	_text->closeWindow();
+	_text->closeWindow(false);
 
 	if (!_text->stringWasEntered()) {
 		// User cancelled? exit now
@@ -454,6 +457,9 @@ int16 SystemUI::askForSavedGameSlot(const char *slotListText) {
 	_text->drawMessageBox(slotListText, messageBoxHeight, 34, true);
 
 	drawSavedGameSlots();
+#ifdef USE_TTS
+	_vm->_queueNextText = true;
+#endif
 	drawSavedGameSlotSelector(true);
 
 	_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT);
@@ -783,6 +789,15 @@ void SystemUI::drawSavedGameSlotSelector(bool active) {
 	}
 	if (active) {
 		_text->displayTextInsideWindow(arrow, windowRow, column);
+
+#ifdef USE_TTS
+		if (_vm->_queueNextText) {
+			_vm->sayText(_savedGameArray[_savedGameSelectedSlotNr].displayText, Common::TextToSpeechManager::QUEUE);
+			_vm->_queueNextText = false;
+		} else {
+			_vm->sayText(_savedGameArray[_savedGameSelectedSlotNr].displayText, Common::TextToSpeechManager::INTERRUPT);
+		}
+#endif
 	} else {
 		_text->displayTextInsideWindow(" ", windowRow, column);
 	}
diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp
index 06773598849..bb047f5e44b 100644
--- a/engines/agi/text.cpp
+++ b/engines/agi/text.cpp
@@ -248,6 +248,26 @@ void TextMgr::display(int16 textNr, int16 textRow, int16 textColumn) {
 		logicTextPtr = _vm->_game._curLogic->texts[textNr - 1];
 		processedTextPtr = stringPrintf(logicTextPtr);
 		processedTextPtr = stringWordWrap(processedTextPtr, 40);
+
+#ifdef USE_TTS
+		if (!_vm->_game.gfxMode) {
+			_vm->sayText(processedTextPtr);
+		} else if (_vm->_game.curLogicNr != 0) {
+			if (_vm->_previousDisplayRow != -1 && textRow != _vm->_previousDisplayRow + 1) {
+				_vm->_combinedText += "\n";
+				_vm->_replaceDisplayNewlines = false;
+			}
+
+			_vm->_combinedText += " ";
+			_vm->_combinedText += processedTextPtr;
+		} else if (_vm->_voiceClock) {
+			_vm->sayText(processedTextPtr);
+			_vm->_voiceClock = false;
+		}
+
+		_vm->_previousDisplayRow = textRow;
+#endif
+
 		displayText(processedTextPtr);
 
 		// Signal, that non-blocking text is shown at the moment
@@ -399,7 +419,8 @@ bool TextMgr::messageBox(const char *textPtr) {
 		_vm->inGameTimerUpdate();
 
 		if (windowTimer > 0) {
-			if (_vm->inGameTimerGetPassedCycles() >= windowTimer) {
+			Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+			if (_vm->inGameTimerGetPassedCycles() >= windowTimer && (!ttsMan || !ttsMan->isSpeaking())) {
 				// Timer reached, close automatically
 				_vm->cycleInnerLoopInactive();
 			}
@@ -510,6 +531,9 @@ void TextMgr::drawMessageBox(const char *textPtr, int16 forcedHeight, int16 want
 
 	_reset_Column = _messageState.textPos.column;
 	displayText(processedTextPtr);
+#ifdef USE_TTS
+	_vm->sayText(processedTextPtr);
+#endif
 	_reset_Column = 0;
 
 	charPos_Pop();
@@ -546,7 +570,7 @@ bool TextMgr::isMouseWithinMessageBox() {
 	return false;
 }
 
-void TextMgr::closeWindow() {
+void TextMgr::closeWindow(bool ttsStopSpeech) {
 	if (_messageState.window_Active) {
 		// Close the window by copying the game screen to the display screen.
 		// Our graphics code was designed with the assumption that the window would
@@ -559,6 +583,13 @@ void TextMgr::closeWindow() {
 		const int16 y = MAX<int16>(0, _messageState.backgroundPos_y);
 		_gfx->render_Block(x, y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height);
 	}
+
+#ifdef USE_TTS
+	if (ttsStopSpeech && (_messageState.dialogue_Open || _messageState.window_Active)) {
+		_vm->stopTextToSpeech();
+	}
+#endif
+
 	_messageState.dialogue_Open = false;
 	_messageState.window_Active = false;
 }
@@ -580,7 +611,7 @@ bool TextMgr::statusEnabled() {
 	return _statusEnabled;
 }
 
-void TextMgr::statusDraw() {
+void TextMgr::statusDraw(bool ttsVoiceScore, bool ttsVoiceSound) {
 	char *statusTextPtr = nullptr;
 
 	charAttrib_Push();
@@ -597,6 +628,12 @@ void TextMgr::statusDraw() {
 			charPos_Set(_statusRow, FONT_COLUMN_CHARACTERS - Common::strnlen(statusTextPtr, FONT_COLUMN_CHARACTERS) - 1);
 		displayText(statusTextPtr);
 
+#ifdef USE_TTS
+		if (ttsVoiceScore) {
+			_vm->sayText(statusTextPtr, Common::TextToSpeechManager::INTERRUPT);
+		}
+#endif
+
 		if (!_vm->isLanguageRTL())
 			charPos_Set(_statusRow, 30);
 		else
@@ -607,6 +644,12 @@ void TextMgr::statusDraw() {
 			statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOff());
 		}
 		displayText(statusTextPtr);
+
+#ifdef USE_TTS
+		if (ttsVoiceSound) {
+			_vm->sayText(statusTextPtr);
+		}
+#endif
 	}
 
 	charPos_Pop();
@@ -632,6 +675,10 @@ void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, i
 	charPos_Clip(row_Upper, column_Upper);
 	charPos_Clip(row_Lower, column_Lower);
 
+#ifdef USE_TTS
+	_vm->_combinedText.clear();
+#endif
+
 	int16 x = column_Upper;
 	int16 y = row_Upper;
 	int16 width = (column_Lower + 1 - column_Upper);
@@ -774,6 +821,9 @@ void TextMgr::promptKeyPress(uint16 newKey) {
 			promptRememberForAutoComplete(true);
 
 			memcpy(&_promptPrevious, &_prompt, sizeof(_prompt));
+#ifdef USE_TTS
+			_vm->sayText((char *)&_prompt);
+#endif
 			// parse text
 			_vm->_words->parseUsingDictionary((char *)&_prompt);
 
@@ -1010,6 +1060,9 @@ void TextMgr::stringKeyPress(uint16 newKey) {
 		stringRememberForAutoComplete(true);
 
 		_inputStringEntered = true;
+#ifdef USE_TTS
+		_vm->sayText((const char *)_inputString);
+#endif
 
 		_vm->cycleInnerLoopInactive(); // exit GetString-loop
 		break;
diff --git a/engines/agi/text.h b/engines/agi/text.h
index c5b5d724b52..ad94a94e84a 100644
--- a/engines/agi/text.h
+++ b/engines/agi/text.h
@@ -138,7 +138,7 @@ public:
 	void drawMessageBox(const char *textPtr, int16 forcedHeight = 0, int16 wantedWidth = 0, bool forcedWidth = false);
 	void getMessageBoxInnerDisplayDimensions(int16 &x, int16 &y, int16 &width, int16 &height);
 	bool isMouseWithinMessageBox();
-	void closeWindow();
+	void closeWindow(bool ttsStopSpeech = true);
 
 	void statusRow_Set(int16 row);
 	int16 statusRow_Get();
@@ -147,7 +147,7 @@ public:
 	void statusDisable();
 	bool statusEnabled();
 
-	void statusDraw();
+	void statusDraw(bool ttsVoiceScore = false, bool ttsVoiceSound = false);
 	void statusClear();
 
 	bool _statusEnabled;




More information about the Scummvm-git-logs mailing list