[Scummvm-git-logs] scummvm master -> 15573cf8a0a7ecbf17c19373e72932e5aa8bcf0b
sev-
noreply at scummvm.org
Thu Aug 21 08:22:49 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:
15573cf8a0 GOB: Add text-to-speech (TTS)
Commit: 15573cf8a0a7ecbf17c19373e72932e5aa8bcf0b
https://github.com/scummvm/scummvm/commit/15573cf8a0a7ecbf17c19373e72932e5aa8bcf0b
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-08-21T10:22:46+02:00
Commit Message:
GOB: Add text-to-speech (TTS)
Changed paths:
engines/gob/detection/detection.cpp
engines/gob/detection/detection.h
engines/gob/draw.h
engines/gob/draw_fascin.cpp
engines/gob/draw_playtoons.cpp
engines/gob/draw_v1.cpp
engines/gob/draw_v2.cpp
engines/gob/game.cpp
engines/gob/gob.cpp
engines/gob/gob.h
engines/gob/hotspots.cpp
engines/gob/hotspots.h
engines/gob/inter_v1.cpp
engines/gob/metaengine.cpp
engines/gob/mult.cpp
engines/gob/util.cpp
engines/gob/videoplayer.cpp
diff --git a/engines/gob/detection/detection.cpp b/engines/gob/detection/detection.cpp
index c220d69b858..9c983c47a87 100644
--- a/engines/gob/detection/detection.cpp
+++ b/engines/gob/detection/detection.cpp
@@ -87,7 +87,7 @@ private:
GobMetaEngineDetection::GobMetaEngineDetection() :
AdvancedMetaEngineDetection(Gob::gameDescriptions, gobGames) {
- _guiOptions = GUIO1(GUIO_NOLAUNCHLOAD);
+ _guiOptions = GUIO2(GUIO_NOLAUNCHLOAD, GAMEOPTION_TTS);
}
ADDetectedGame GobMetaEngineDetection::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist, ADDetectedGameExtraInfo **extra) const {
diff --git a/engines/gob/detection/detection.h b/engines/gob/detection/detection.h
index 452677baa58..3727cf090ee 100644
--- a/engines/gob/detection/detection.h
+++ b/engines/gob/detection/detection.h
@@ -107,6 +107,7 @@ struct GOBGameDescription {
};
#define GAMEOPTION_COPY_PROTECTION GUIO_GAMEOPTIONS1
+#define GAMEOPTION_TTS GUIO_GAMEOPTIONS2
} // End of namespace Gob
diff --git a/engines/gob/draw.h b/engines/gob/draw.h
index c2e8e50e368..00a8a370dec 100644
--- a/engines/gob/draw.h
+++ b/engines/gob/draw.h
@@ -225,7 +225,7 @@ public:
virtual void animateCursor(int16 cursor) = 0;
virtual void printTotText(int16 id) = 0;
- virtual void spriteOperation(int16 operation) = 0;
+ virtual void spriteOperation(int16 operation, bool ttsAddHotspotText = true) = 0;
virtual int16 openWin(int16 id) { return 0; }
virtual void closeWin(int16 id) {}
@@ -241,6 +241,9 @@ public:
protected:
GobEngine *_vm;
+#ifdef USE_TTS
+ Common::String _previousTot;
+#endif
};
class Draw_v1 : public Draw {
@@ -250,7 +253,7 @@ public:
void blitCursor() override;
void animateCursor(int16 cursor) override;
void printTotText(int16 id) override;
- void spriteOperation(int16 operation) override;
+ void spriteOperation(int16 operation, bool ttsAddHotspotText = true) override;
Draw_v1(GobEngine *vm);
~Draw_v1() override {}
@@ -263,7 +266,7 @@ public:
void blitCursor() override;
void animateCursor(int16 cursor) override;
void printTotText(int16 id) override;
- void spriteOperation(int16 operation) override;
+ void spriteOperation(int16 operation, bool ttsAddHotspotText = true) override;
Draw_v2(GobEngine *vm);
~Draw_v2() override {}
@@ -286,7 +289,7 @@ class Draw_Fascination: public Draw_v2 {
public:
Draw_Fascination(GobEngine *vm);
~Draw_Fascination() override {}
- void spriteOperation(int16 operation) override;
+ void spriteOperation(int16 operation, bool ttsAddHotspotText = true) override;
void decompWin(int16 x, int16 y, SurfacePtr destPtr);
void drawWin(int16 fct);
@@ -310,7 +313,7 @@ class Draw_Playtoons: public Draw_v2 {
public:
Draw_Playtoons(GobEngine *vm);
~Draw_Playtoons() override {}
- void spriteOperation(int16 operation) override;
+ void spriteOperation(int16 operation, bool ttsAddHotspotText = true) override;
};
diff --git a/engines/gob/draw_fascin.cpp b/engines/gob/draw_fascin.cpp
index 2f1203ebc9a..ac09b9079e0 100644
--- a/engines/gob/draw_fascin.cpp
+++ b/engines/gob/draw_fascin.cpp
@@ -28,6 +28,7 @@
#include "gob/draw.h"
#include "gob/game.h"
#include "gob/global.h"
+#include "gob/hotspots.h"
#include "gob/inter.h"
#include "gob/resources.h"
@@ -36,7 +37,7 @@ namespace Gob {
Draw_Fascination::Draw_Fascination(GobEngine *vm) : Draw_v2(vm) {
}
-void Draw_Fascination::spriteOperation(int16 operation) {
+void Draw_Fascination::spriteOperation(int16 operation, bool ttsAddHotspotText) {
int16 len;
int16 x, y;
SurfacePtr sourceSurf, destSurf;
@@ -257,6 +258,13 @@ void Draw_Fascination::spriteOperation(int16 operation) {
}
}
+#ifdef USE_TTS
+ if (ttsAddHotspotText) {
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, left, _destSpriteY,
+ _destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1, _destSurface);
+ }
+#endif
+
dirtiedRect(_destSurface, left, _destSpriteY,
_destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1);
break;
@@ -542,6 +550,12 @@ void Draw_Fascination::drawWin(int16 fct) {
_fonts[_fontIndex]->drawLetter(*tempSrf, _textToPrint[j],
j * _fonts[_fontIndex]->getCharWidth(), 0, _frontColor, _backColor, _transparency);
_destSpriteX += len * _fonts[_fontIndex]->getCharWidth();
+
+#ifdef USE_TTS
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, left, _destSpriteY,
+ _destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1, _destSurface);
+#endif
+
break;
case DRAW_DRAWBAR: // 7 - draw border
@@ -660,6 +674,12 @@ void Draw_Fascination::drawWin(int16 fct) {
_destSpriteX + j * _fonts[_fontIndex]->getCharWidth(), _destSpriteY,
_frontColor, _backColor, _transparency);
_destSpriteX += len * _fonts[_fontIndex]->getCharWidth();
+
+#ifdef USE_TTS
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, left, _destSpriteY,
+ _destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1, _destSurface);
+#endif
+
break;
case DRAW_DRAWBAR: // 7 - draw border
diff --git a/engines/gob/draw_playtoons.cpp b/engines/gob/draw_playtoons.cpp
index ac439173a72..2bf25312b40 100644
--- a/engines/gob/draw_playtoons.cpp
+++ b/engines/gob/draw_playtoons.cpp
@@ -31,13 +31,14 @@
#include "gob/inter.h"
#include "gob/game.h"
#include "gob/resources.h"
+#include "gob/hotspots.h"
namespace Gob {
Draw_Playtoons::Draw_Playtoons(GobEngine *vm) : Draw_v2(vm) {
}
-void Draw_Playtoons::spriteOperation(int16 operation) {
+void Draw_Playtoons::spriteOperation(int16 operation, bool ttsAddHotspotText) {
int16 len;
int16 x, y;
bool deltaVeto;
@@ -350,6 +351,13 @@ void Draw_Playtoons::spriteOperation(int16 operation) {
}
}
+#ifdef USE_TTS
+ if (ttsAddHotspotText) {
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, left, _destSpriteY,
+ _destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1, _destSurface);
+ }
+#endif
+
dirtiedRect(_destSurface, left, _destSpriteY,
_destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1);
break;
diff --git a/engines/gob/draw_v1.cpp b/engines/gob/draw_v1.cpp
index 1cecff2583e..ebda9c4b480 100644
--- a/engines/gob/draw_v1.cpp
+++ b/engines/gob/draw_v1.cpp
@@ -233,6 +233,9 @@ void Draw_v1::printTotText(int16 id) {
}
ptrEnd++;
+#ifdef USE_TTS
+ Common::String ttsMessage;
+#endif
while (*ptr != 1) {
cmd = *ptr;
if (cmd == 3) {
@@ -281,8 +284,13 @@ void Draw_v1::printTotText(int16 id) {
}
_textToPrint = buf;
+#ifdef USE_TTS
+ ttsMessage += _textToPrint;
+ ttsMessage += " ";
+#endif
+
destSpriteX = _destSpriteX;
- spriteOperation(DRAW_PRINTTEXT);
+ spriteOperation(DRAW_PRINTTEXT, false);
if (ptrEnd[17] & 0x80) {
if (ptr[1] == ' ') {
_destSpriteX += _fonts[_fontIndex]->getCharWidth();
@@ -304,6 +312,18 @@ void Draw_v1::printTotText(int16 id) {
}
}
+#ifdef USE_TTS
+ if (_previousTot != ttsMessage) {
+ if (_vm->_game->_hotspots->hoveringOverHotspot()) {
+ _vm->sayText(ttsMessage);
+ } else {
+ _vm->sayText(ttsMessage, Common::TextToSpeechManager::QUEUE);
+ }
+
+ _previousTot = ttsMessage;
+ }
+#endif
+
delete textItem;
_renderFlags = savedFlags;
@@ -316,7 +336,7 @@ void Draw_v1::printTotText(int16 id) {
}
}
-void Draw_v1::spriteOperation(int16 operation) {
+void Draw_v1::spriteOperation(int16 operation, bool ttsAddHotspotText) {
int16 len;
int16 x, y;
int16 perLine;
@@ -416,6 +436,14 @@ void Draw_v1::spriteOperation(int16 operation) {
_destSpriteX + len * font->getCharWidth() - 1,
_destSpriteY + font->getCharHeight() - 1);
+#ifdef USE_TTS
+ if (ttsAddHotspotText) {
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, _destSpriteX, _destSpriteY,
+ _destSpriteX + len * font->getCharWidth() - 1,
+ _destSpriteY + font->getCharHeight() - 1, _destSurface);
+ }
+#endif
+
for (int i = 0; i < len; i++) {
font->drawLetter(*_spritesArray[_destSurface], _textToPrint[i],
_destSpriteX, _destSpriteY, _frontColor, _backColor, _transparency);
diff --git a/engines/gob/draw_v2.cpp b/engines/gob/draw_v2.cpp
index 9cf8b2d85ea..91a5636ea5e 100644
--- a/engines/gob/draw_v2.cpp
+++ b/engines/gob/draw_v2.cpp
@@ -395,6 +395,9 @@ void Draw_v2::printTotText(int16 id) {
_backColor = 0;
_transparency = 1;
+#ifdef USE_TTS
+ Common::String ttsMessage;
+#endif
while (true) {
if ((((*ptr >= 1) && (*ptr <= 7)) || (*ptr == 10)) && (strPos != 0)) {
str[MAX(strPos, strPos2)] = 0;
@@ -429,6 +432,10 @@ void Draw_v2::printTotText(int16 id) {
_fontIndex = fontIndex;
_frontColor = frontColor;
_textToPrint = str;
+#ifdef USE_TTS
+ ttsMessage += _textToPrint;
+ ttsMessage += " ";
+#endif
if (isSubtitle) {
_fontIndex = _subtitleFont;
@@ -442,10 +449,10 @@ void Draw_v2::printTotText(int16 id) {
width -= _fonts[_fontIndex]->getCharWidth() / 2;
str[strlen(str) - 1] = '\0';
}
- spriteOperation(DRAW_PRINTTEXT);
+ spriteOperation(DRAW_PRINTTEXT, false);
}
} else
- spriteOperation(DRAW_PRINTTEXT);
+ spriteOperation(DRAW_PRINTTEXT, false);
width = strlen(str);
for (strPos = 0; strPos < width; strPos++) {
@@ -624,6 +631,18 @@ void Draw_v2::printTotText(int16 id) {
}
}
+#ifdef USE_TTS
+ if (_previousTot != ttsMessage && !isSubtitle) {
+ if (_vm->_game->_hotspots->hoveringOverHotspot()) {
+ _vm->sayText(ttsMessage);
+ } else {
+ _vm->sayText(ttsMessage, Common::TextToSpeechManager::QUEUE);
+ }
+
+ _previousTot = ttsMessage;
+ }
+#endif
+
delete textItem;
_renderFlags = savedFlags;
@@ -638,7 +657,7 @@ void Draw_v2::printTotText(int16 id) {
}
}
-void Draw_v2::spriteOperation(int16 operation) {
+void Draw_v2::spriteOperation(int16 operation, bool ttsAddHotspotText) {
int16 len;
int16 x, y;
SurfacePtr sourceSurf, destSurf;
@@ -868,6 +887,23 @@ void Draw_v2::spriteOperation(int16 operation) {
}
}
+#ifdef USE_TTS
+ // Ween's notepad displays 1 character at a time. Stopping speech as the characters are displayed prevents TTS from
+ // slowly voicing each character
+ if (_vm->getGameType() == kGameTypeWeen && _vm->isCurrentTot("edit.tot")) {
+ _vm->stopTextToSpeech();
+
+ if (!_vm->_weenVoiceNotepad) {
+ ttsAddHotspotText = false;
+ }
+ }
+
+ if (ttsAddHotspotText) {
+ _vm->_game->_hotspots->addHotspotText(_textToPrint, left, _destSpriteY,
+ _destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1, _destSurface);
+ }
+#endif
+
dirtiedRect(_destSurface, left, _destSpriteY,
_destSpriteX - 1, _destSpriteY + _fonts[_fontIndex]->getCharHeight() - 1);
break;
diff --git a/engines/gob/game.cpp b/engines/gob/game.cpp
index d6eb63ce2e2..ef92f2c54bd 100644
--- a/engines/gob/game.cpp
+++ b/engines/gob/game.cpp
@@ -685,6 +685,13 @@ void Game::playTot(int32 function) {
_vm->_inter->_terminate = 2;
}
+#ifdef USE_TTS
+ if (_vm->getGameType() == kGameTypeWeen && _vm->isCurrentTot("edit.tot")) {
+ _vm->_weenVoiceNotepad = true;
+ _vm->_game->_hotspots->clearHotspotText();
+ }
+#endif
+
_curTotFile = oldTotFile;
_vm->_inter->_nestLevel = oldNestLevel;
@@ -755,6 +762,10 @@ void Game::capturePop(char doDraw) {
_vm->_draw->_needAdjust = 10;
_vm->_draw->spriteOperation(DRAW_BLITSURF);
_vm->_draw->_needAdjust = savedNeedAdjust;
+
+#ifdef USE_TTS
+ _hotspots->clearHotspotText();
+#endif
}
_vm->_draw->freeSprite(Draw::kCaptureSurface + _captureCount);
}
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index 6d4b0013ba1..42e3938002a 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -40,6 +40,7 @@
#include "gob/gob.h"
#include "gob/global.h"
+#include "gob/hotspots.h"
#include "gob/util.h"
#include "gob/dataio.h"
#include "gob/game.h"
@@ -141,6 +142,10 @@ GobEngine::GobEngine(OSystem *syst) : Engine(syst), _rnd("gob") {
_copyProtection = ConfMan.getBool("copy_protection");
+#ifdef USE_TTS
+ _weenVoiceNotepad = true;
+#endif
+
_console = new GobConsole(this);
setDebugger(_console);
}
@@ -384,6 +389,35 @@ Common::Error GobEngine::run() {
}
_global->_languageWanted = _global->_language;
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan) {
+ ttsMan->setLanguage(ConfMan.get("language"));
+ ttsMan->enable(ConfMan.getBool("tts_enabled"));
+ }
+
+ switch (_language) {
+ case Common::HU_HUN:
+ _ttsEncoding = Common::CodePage::kWindows1250;
+ break;
+ case Common::KO_KOR:
+ _ttsEncoding = Common::CodePage::kWindows949;
+ break;
+ case Common::HE_ISR:
+ _ttsEncoding = Common::CodePage::kDos862;
+ break;
+ case Common::JA_JPN:
+ _ttsEncoding = Common::CodePage::kWindows932;
+ break;
+ case Common::RU_RUS:
+ _ttsEncoding = Common::CodePage::kWindows1251;
+ break;
+ default:
+ _ttsEncoding = Common::CodePage::kDos850;
+ break;
+ }
+#endif
+
_init->initGame();
return Common::kNoError;
@@ -427,6 +461,29 @@ void GobEngine::pauseGame() {
pauseEngineIntern(false);
}
+#ifdef USE_TTS
+
+void GobEngine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
+ if (text.empty()) {
+ return;
+ }
+
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan && ConfMan.getBool("tts_enabled")) {
+ ttsMan->say(text, action, _ttsEncoding);
+ }
+}
+
+void GobEngine::stopTextToSpeech() const {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+ ttsMan->stop();
+ _game->_hotspots->clearPreviousSaid();
+ }
+}
+
+#endif
+
Common::Error GobEngine::initGameParts() {
_resourceSizeWorkaround = false;
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index a6e0b2e80e7..eabde573e39 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -30,6 +30,7 @@
#include "common/random.h"
#include "common/system.h"
+#include "common/text-to-speech.h"
#include "graphics/pixelformat.h"
@@ -227,12 +228,22 @@ public:
VideoPlayer *_vidPlayer;
PreGob *_preGob;
+#ifdef USE_TTS
+ bool _weenVoiceNotepad;
+ Common::CodePage _ttsEncoding;
+#endif
+
const char *getLangDesc(int16 language) const;
void validateLanguage();
void validateVideoMode(int16 videoMode);
void pauseGame();
+#ifdef USE_TTS
+ void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT) const;
+ void stopTextToSpeech() const;
+#endif
+
EndiannessMethod getEndiannessMethod() const;
Endianness getEndianness() const;
Common::Platform getPlatform() const;
diff --git a/engines/gob/hotspots.cpp b/engines/gob/hotspots.cpp
index d71b57ea158..0412462c7a3 100644
--- a/engines/gob/hotspots.cpp
+++ b/engines/gob/hotspots.cpp
@@ -211,6 +211,10 @@ Hotspots::Hotspots(GobEngine *vm) : _vm(vm) {
_currentId = 0;
_currentX = 0;
_currentY = 0;
+#ifdef USE_TTS
+ _currentHotspotTextIndex = -1;
+ _hotspotSpokenLast = false;
+#endif
}
Hotspots::~Hotspots() {
@@ -265,6 +269,11 @@ uint16 Hotspots::add(const Hotspot &hotspot) {
spot.scriptFuncPos = _vm->_game->_script;
spot.scriptFuncLeave = _vm->_game->_script;
+#ifdef USE_TTS
+ removeHotspotText(i);
+ expandHotspotText(i);
+#endif
+
debugC(1, kDebugHotspots, "Adding hotspot %03d: Coord:%3d+%3d+%3d+%3d - id:%04X, key:%04X, flag:%04X - fcts:%5d, %5d, %5d",
i, spot.left, spot.top, spot.right, spot.bottom,
spot.id, spot.key, spot.flags, spot.funcEnter, spot.funcLeave, spot.funcPos);
@@ -281,6 +290,9 @@ void Hotspots::remove(uint16 id) {
if (_hotspots[i].id == id) {
debugC(1, kDebugHotspots, "Removing hotspot %d: %X", i, id);
_hotspots[i].clear();
+#ifdef USE_TTS
+ removeHotspotText(i);
+#endif
}
}
}
@@ -292,6 +304,9 @@ void Hotspots::removeState(uint8 state) {
if (spot.getState() == state) {
debugC(1, kDebugHotspots, "Removing hotspot %d: %X (by state %X)", i, spot.id, state);
spot.clear();
+#ifdef USE_TTS
+ removeHotspotText(i);
+#endif
}
}
}
@@ -371,6 +386,11 @@ void Hotspots::recalculate(bool force) {
_vm->_game->_script = curScript;
}
+
+#ifdef USE_TTS
+ voiceUnassignedHotspots();
+ clearUnassignedHotspotText();
+#endif
}
void Hotspots::push(uint8 all, bool force) {
@@ -380,6 +400,10 @@ void Hotspots::push(uint8 all, bool force) {
if (!_shouldPush && !force)
return;
+#ifdef USE_TTS
+ voiceUnassignedHotspots();
+#endif
+
// Count the hotspots
uint32 size = 0;
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
@@ -448,6 +472,10 @@ void Hotspots::pop() {
assert(!_stack.empty());
+#ifdef USE_TTS
+ voiceUnassignedHotspots();
+#endif
+
StackEntry backup = _stack.pop();
// Find the end of the filled hotspot space
@@ -539,6 +567,10 @@ void Hotspots::leave(uint16 index) {
return;
}
+#ifdef USE_TTS
+ clearPreviousSaid();
+#endif
+
Hotspot &spot = _hotspots[index];
// If requested, write the ID into a variable
@@ -1238,8 +1270,12 @@ uint16 Hotspots::handleInputs(int16 time, uint16 inputCount, uint16 &curInput,
case kKeyReturn:
// Just one input => return
- if (inputCount == 1)
+ if (inputCount == 1) {
+#ifdef USE_TTS
+ _vm->sayText(GET_VARO_STR(inputSpot.key));
+#endif
return kKeyReturn;
+ }
// End of input chain reached => wrap
if (curInput == (inputCount - 1)) {
@@ -1655,6 +1691,11 @@ void Hotspots::evaluate() {
// Recalculate all hotspots if requested
if (needRecalculation)
recalculate(true);
+#ifdef USE_TTS
+ else {
+ voiceUnassignedHotspots();
+ }
+#endif
_vm->_game->_forceHandleMouse = 0;
_vm->_util->clearKeyBuf();
@@ -1735,6 +1776,9 @@ void Hotspots::evaluate() {
spot.disable();
}
+#ifdef USE_TTS
+ clearUnassignedHotspotText();
+#endif
}
int16 Hotspots::findCursor(uint16 x, uint16 y) const {
@@ -1863,6 +1907,119 @@ void Hotspots::oPlaytoons_F_1B() {
return;
}
+#ifdef USE_TTS
+
+bool Hotspots::hoveringOverHotspot() const {
+ return _currentIndex != 0;
+}
+
+void Hotspots::addHotspotText(const Common::String &text, uint16 x1, uint16 y1, uint16 x2, uint16 y2, int16 surf) {
+ if (x1 <= x2 && y1 <= y2) {
+ Common::Rect rect(x1, y1, x2, y2);
+ for (uint i = 0; i < _hotspotText.size(); ++i) {
+ // If there's already hotspot text at the same position, simply change the text
+ if (rect.contains(_hotspotText[i].rect) || _hotspotText[i].rect.contains(rect)) {
+ _hotspotText[i].str = text;
+ _hotspotText[i].voiced = false;
+ return;
+ }
+ }
+
+ _hotspotText.push_back(HotspotTTSText{text, rect, -1, false, surf});
+ } else {
+ _vm->sayText(text, Common::TextToSpeechManager::QUEUE);
+ }
+}
+
+void Hotspots::voiceHotspotText(int16 x, int16 y) {
+ // Don't allow any text on Ween's notepad to be voiced by moving the mouse, as the text is typically broken into pieces,
+ // which results in awkward voicing
+ if (_vm->getGameType() == kGameTypeWeen && _vm->isCurrentTot("edit.tot")) {
+ return;
+ }
+
+ for (uint i = 0; i < _hotspotText.size(); ++i) {
+ if (_hotspotText[i].rect.contains(x, y)) {
+ if ((int16)i != _currentHotspotTextIndex) {
+ _vm->sayText(_hotspotText[i].str, _hotspotSpokenLast ? Common::TextToSpeechManager::INTERRUPT : Common::TextToSpeechManager::QUEUE);
+ _hotspotSpokenLast = true;
+ _currentHotspotTextIndex = i;
+ }
+
+ return;
+ }
+ }
+
+ _currentHotspotTextIndex = -1;
+ if (!hoveringOverHotspot()) {
+ clearPreviousSaid();
+ }
+}
+
+
+void Hotspots::voiceUnassignedHotspots() {
+ Common::String ttsMessage;
+ for (uint i = 0; i < _hotspotText.size(); ++i) {
+ // For Ween's notepad, manually add spaces back in
+ if (_vm->getGameType() == kGameTypeWeen && _vm->isCurrentTot("edit.tot")) {
+ if (_vm->_weenVoiceNotepad) {
+ if (_vm->_draw->_fontIndex < _vm->_draw->kFontCount && _vm->_draw->_fonts[_vm->_draw->_fontIndex]) {
+ if (i > 0 &&
+ _hotspotText[i].rect.left !=
+ _hotspotText[i - 1].rect.left + _vm->_draw->_fonts[_vm->_draw->_fontIndex]->getCharWidth()) {
+ ttsMessage += " ";
+ }
+ }
+
+ ttsMessage += _hotspotText[i].str;
+ }
+ } else if (_hotspotText[i].hotspot == -1 && !_hotspotText[i].voiced) {
+ ttsMessage += _hotspotText[i].str + "\n";
+ _hotspotText[i].voiced = true;
+ }
+ }
+
+ if (_previousSaid != ttsMessage && !ttsMessage.empty()) {
+ _hotspotSpokenLast = false;
+ _vm->sayText(ttsMessage, Common::TextToSpeechManager::QUEUE);
+ _previousSaid = ttsMessage;
+ }
+}
+
+void Hotspots::clearHotspotText() {
+ _hotspotText.clear();
+}
+
+void Hotspots::clearUnassignedHotspotText() {
+ for (Common::Array<HotspotTTSText>::iterator it = _hotspotText.begin(); it != _hotspotText.end();) {
+ if (it->hotspot == -1) {
+ it = _hotspotText.erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
+void Hotspots::clearPreviousSaid() {
+ _previousSaid.clear();
+}
+
+void Hotspots::adjustHotspotTextRect(uint16 oldLeft, uint16 oldTop, uint16 oldRight, uint16 oldBottom, uint16 newX, uint16 newY, int16 surf) {
+ if (oldLeft > oldRight || oldTop > oldBottom) {
+ return;
+ }
+
+ Common::Rect oldRect(oldLeft, oldTop, oldRight, oldBottom);
+ for (uint i = 0; i < _hotspotText.size(); ++i) {
+ if (_hotspotText[i].surf == surf && oldRect.contains(_hotspotText[i].rect)) {
+ _hotspotText[i].rect.moveTo(newX, newY);
+ return;
+ }
+ }
+}
+
+#endif
+
uint16 Hotspots::inputToHotspot(uint16 input) const {
uint16 inputIndex = 0;
for (int i = 0; i < kHotspotCount; i++) {
@@ -2272,4 +2429,69 @@ void Hotspots::updateAllTexts(const InputDesc *inputs) const {
input++;
}
}
+
+#ifdef USE_TTS
+
+void Hotspots::expandHotspotText(uint16 spotID) {
+ if (spotID > kHotspotCount || _hotspots[spotID].getType() == kTypeClick || _hotspots[spotID].isDisabled()) {
+ return;
+ }
+
+ if (_vm->getGameType() == kGameTypeFascination) {
+ // Don't include hotspots that aren't on the current window
+ uint16 windowNum = _hotspots[spotID].getWindow();
+ if (windowNum != 0 && (_vm->_draw->_fascinWin[windowNum].id == -1 ||
+ _vm->_draw->_fascinWin[windowNum].id != _vm->_draw->_winCount - 1)) {
+ return;
+ }
+ }
+
+ Common::Rect spotRect;
+ spotRect.left = _hotspots[spotID].left;
+ spotRect.top = _hotspots[spotID].top;
+ spotRect.right = _hotspots[spotID].right;
+ spotRect.bottom = _hotspots[spotID].bottom;
+
+ if (!spotRect.isValidRect()) {
+ return;
+ }
+
+ for (int i = _hotspotText.size() - 1; i >= 0; --i) {
+ if (_hotspotText[i].hotspot != -1) {
+ continue;
+ }
+
+ if (spotRect.intersects(_hotspotText[i].rect)) {
+ _hotspotText[i].rect = spotRect;
+ _hotspotText[i].hotspot = spotID;
+
+ // Try to voice what the mouse is hovering over, as the text underneath the mouse may have changed
+ int16 dx = 0;
+ int16 dy = 0;
+ if (_vm->_draw->getWinFromCoord(dx, dy) < 0) {
+ dx = 0;
+ dy = 0;
+ }
+
+ voiceHotspotText(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy);
+ return;
+ }
+ }
+}
+
+void Hotspots::removeHotspotText(uint16 spotID) {
+ for (uint i = 0; i < _hotspotText.size(); ++i) {
+ if (_hotspotText[i].hotspot == spotID) {
+ _hotspotText.remove_at(i);
+
+ if ((int16)i == _currentHotspotTextIndex) {
+ _currentHotspotTextIndex = -1;
+ }
+ return;
+ }
+ }
+}
+
+#endif
+
} // End of namespace Gob
diff --git a/engines/gob/hotspots.h b/engines/gob/hotspots.h
index 6e8375c8802..d49cc1ff88c 100644
--- a/engines/gob/hotspots.h
+++ b/engines/gob/hotspots.h
@@ -106,6 +106,17 @@ public:
/** implementation of oPlaytoons_F_1B code*/
void oPlaytoons_F_1B();
+#ifdef USE_TTS
+ bool hoveringOverHotspot() const;
+ void addHotspotText(const Common::String &text, uint16 x1, uint16 y1, uint16 x2, uint16 y2, int16 surf);
+ void voiceUnassignedHotspots();
+ void voiceHotspotText(int16 x, int16 y);
+ void clearHotspotText();
+ void clearUnassignedHotspotText();
+ void clearPreviousSaid();
+ void adjustHotspotTextRect(uint16 oldLeft, uint16 oldTop, uint16 oldRight, uint16 oldBottom, uint16 newX, uint16 newY, int16 surf);
+#endif
+
private:
struct Hotspot {
uint16 id;
@@ -157,6 +168,16 @@ private:
void enable ();
};
+#ifdef USE_TTS
+ struct HotspotTTSText {
+ Common::String str;
+ Common::Rect rect;
+ int16 hotspot;
+ bool voiced;
+ int16 surf;
+ };
+#endif
+
struct StackEntry {
bool shouldPush;
Hotspot *hotspots;
@@ -189,6 +210,13 @@ private:
uint16 _currentX;
uint16 _currentY;
+#ifdef USE_TTS
+ Common::String _previousSaid;
+ int16 _currentHotspotTextIndex;
+ bool _hotspotSpokenLast;
+ Common::Array<HotspotTTSText> _hotspotText;
+#endif
+
/** Add a hotspot, returning the new index. */
uint16 add(const Hotspot &hotspot);
@@ -278,6 +306,11 @@ private:
/** Go through all inputs we manage and redraw their texts. */
void updateAllTexts(const InputDesc *inputs) const;
+
+#ifdef USE_TTS
+ void expandHotspotText(uint16 spotID);
+ void removeHotspotText(uint16 spotID);
+#endif
};
} // End of namespace Gob
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index 5f0578fa4d3..59d35460c11 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -1361,12 +1361,19 @@ void Inter_v1::o1_keyFunc(OpFuncParams ¶ms) {
int16 cmd = _vm->_game->_script->readInt16();
int16 key;
+#ifdef USE_TTS
+ _vm->_game->_hotspots->voiceUnassignedHotspots();
+#endif
switch (cmd) {
case 0:
_vm->_draw->_showCursor &= ~2;
_vm->_util->longDelay(1);
key = _vm->_game->_hotspots->check(0, 0);
storeKey(key);
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+ _vm->_game->_hotspots->clearHotspotText();
+#endif
_vm->_util->clearKeyBuf();
break;
@@ -1395,6 +1402,17 @@ void Inter_v1::o1_keyFunc(OpFuncParams ¶ms) {
key = _vm->_game->checkKeys(&_vm->_global->_inter_mouseX,
&_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons, 0);
storeKey(key);
+#ifdef USE_TTS
+ if (key) {
+ // After a key is pressed with the notepad open, no longer voice the notepad. This prevents very awkward voicing
+ // as the user types
+ if (_vm->getGameType() == kGameTypeWeen && _vm->isCurrentTot("edit.tot")) {
+ _vm->_weenVoiceNotepad = false;
+ }
+
+ _vm->stopTextToSpeech();
+ }
+#endif
break;
case 2:
@@ -1657,6 +1675,13 @@ void Inter_v1::o1_copySprite(OpFuncParams ¶ms) {
return;
}
+#ifdef USE_TTS
+ _vm->_game->_hotspots->adjustHotspotTextRect(_vm->_draw->_spriteLeft, _vm->_draw->_spriteTop,
+ _vm->_draw->_spriteLeft + _vm->_draw->_spriteRight - 1,
+ _vm->_draw->_spriteTop + _vm->_draw->_spriteBottom - 1,
+ _vm->_draw->_destSpriteX, _vm->_draw->_destSpriteY, _vm->_draw->_sourceSurface);
+#endif
+
_vm->_draw->spriteOperation(DRAW_BLITSURF);
}
diff --git a/engines/gob/metaengine.cpp b/engines/gob/metaengine.cpp
index 7fe879a6abc..2d6c8b5844c 100644
--- a/engines/gob/metaengine.cpp
+++ b/engines/gob/metaengine.cpp
@@ -50,6 +50,20 @@ static const ADExtraGuiOptionsMap optionsList[] = {
0
},
},
+#ifdef USE_TTS
+ {
+ GAMEOPTION_TTS,
+ {
+ _s("Enable Text to Speech"),
+ _s("Use TTS to read text in the game (if TTS is available)"),
+ "tts_enabled",
+ false,
+ 0,
+ 0
+ }
+ },
+#endif
+
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp
index f254413eb93..4a341025d7c 100644
--- a/engines/gob/mult.cpp
+++ b/engines/gob/mult.cpp
@@ -180,6 +180,7 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,
if (_frame == -1)
playMultInit();
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
do {
stop = true;
@@ -209,13 +210,19 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,
if (_vm->_sound->blasterPlayingSound())
stop = false;
- _vm->_util->processInput();
- if (checkEscape && (_vm->_util->checkKey() == kKeyEscape))
- stop = true;
+ do {
+ _vm->_util->processInput();
+ if (checkEscape && (_vm->_util->checkKey() == kKeyEscape))
+ stop = true;
+
+ _vm->_util->waitEndFrame();
+ } while (!stop && stopNoClear && ttsMan && ttsMan->isSpeaking());
_frame++;
- _vm->_util->waitEndFrame();
} while (!stop && !stopNoClear && !_vm->shouldQuit());
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+#endif
if (!stopNoClear) {
if (_animDataAllocated) {
diff --git a/engines/gob/util.cpp b/engines/gob/util.cpp
index edd8fc005cb..4e9a158a186 100644
--- a/engines/gob/util.cpp
+++ b/engines/gob/util.cpp
@@ -30,6 +30,7 @@
#include "graphics/paletteman.h"
#include "gob/gob.h"
+#include "gob/hotspots.h"
#include "gob/util.h"
#include "gob/global.h"
#include "gob/dataio.h"
@@ -371,6 +372,10 @@ void Util::setMousePos(int16 x, int16 y) {
x = CLIP<int>(x + _vm->_video->_screenDeltaX, 0, _vm->_width - 1);
y = CLIP<int>(y + _vm->_video->_screenDeltaY, 0, _vm->_height - 1);
g_system->warpMouse(x, y);
+
+#ifdef USE_TTS
+ _vm->_game->_hotspots->voiceHotspotText(x, y);
+#endif
}
void Util::waitMouseUp() {
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 5b7a57042dd..b71f3faebd4 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -808,12 +808,18 @@ void VideoPlayer::checkAbort(Video &video, Properties &properties) {
if (pressedBreak ||
_vm->_game->_mouseButtons == properties.breakKey) {
properties.canceled = true;
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+#endif
return;
}
if (properties.breakKey == 4) {
if (_vm->_game->_mouseButtons == kMouseButtonsRight || key == kKeyEscape) {
properties.canceled = true;
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+#endif
return;
}
@@ -824,6 +830,9 @@ void VideoPlayer::checkAbort(Video &video, Properties &properties) {
_vm->_game->_forwardedKeyFromVideo = key;
_vm->_game->_forwardedMouseButtonsFromVideo = _vm->_game->_mouseButtons;
properties.canceled = true;
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+#endif
return;
}
}
@@ -844,6 +853,9 @@ void VideoPlayer::checkAbort(Video &video, Properties &properties) {
// Seek to the last frame. Some scripts depend on that.
video.decoder->seek(properties.endFrame + 1, SEEK_SET, true);
properties.canceled = true;
+#ifdef USE_TTS
+ _vm->stopTextToSpeech();
+#endif
}
}
}
More information about the Scummvm-git-logs
mailing list