[Scummvm-git-logs] scummvm master -> 8b8d72347d0a524e4721393b3fbd98461b4ba10f
sev-
noreply at scummvm.org
Mon Jul 21 10:57:52 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:
8b8d72347d PARALLACTION: Add text-to-speech (TTS)
Commit: 8b8d72347d0a524e4721393b3fbd98461b4ba10f
https://github.com/scummvm/scummvm/commit/8b8d72347d0a524e4721393b3fbd98461b4ba10f
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-07-21T12:57:49+02:00
Commit Message:
PARALLACTION: Add text-to-speech (TTS)
Changed paths:
engines/parallaction/balloons.cpp
engines/parallaction/callables_ns.cpp
engines/parallaction/detection.cpp
engines/parallaction/detection.h
engines/parallaction/dialogue.cpp
engines/parallaction/exec.h
engines/parallaction/exec_ns.cpp
engines/parallaction/graphics.cpp
engines/parallaction/graphics.h
engines/parallaction/gui_ns.cpp
engines/parallaction/metaengine.cpp
engines/parallaction/parallaction.cpp
engines/parallaction/parallaction.h
engines/parallaction/parallaction_ns.cpp
diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp
index 16c5f71ffb5..305c458dc3a 100644
--- a/engines/parallaction/balloons.cpp
+++ b/engines/parallaction/balloons.cpp
@@ -334,6 +334,8 @@ int BalloonManager_ns::setSingleBalloon(const Common::String &text, uint16 x, ui
w = _se.width() + 14;
h = _se.height() + 20;
+ _vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
+
int id = createBalloon(w+5, h, winding, 1);
Balloon *balloon = &_intBalloons[id];
@@ -380,6 +382,10 @@ void BalloonManager_ns::setBalloonText(uint id, const Common::String &text, Text
Balloon *balloon = getBalloon(id);
balloon->surface->fillRect(balloon->innerBox, 1);
+ if (textColor != kUnselectedColor && !text.contains("%P")) {
+ _vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
+ }
+
_sw.write(text, MAX_BALLOON_WIDTH, _textColors[textColor], balloon->surface);
}
@@ -426,6 +432,10 @@ void BalloonManager_ns::reset() {
_intBalloons[i].surface = nullptr; // no need to delete surface, since it is done by Gfx
}
_numBalloons = 0;
+
+ if (_vm->_password.size() == 0) {
+ _vm->stopTextToSpeech();
+ }
}
@@ -585,6 +595,8 @@ Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum)
int BalloonManager_br::setSingleBalloon(const Common::String &text, uint16 x, uint16 y, uint16 winding, TextColor textColor) {
cacheAnims();
+ _vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
+
int id = _numBalloons;
Frames *src = nullptr;
int srcFrame = 0;
@@ -657,6 +669,11 @@ int BalloonManager_br::setDialogueBalloon(const Common::String &text, uint16 win
void BalloonManager_br::setBalloonText(uint id, const Common::String &text, TextColor textColor) {
Balloon *balloon = getBalloon(id);
+
+ if (textColor != kUnselectedColor) {
+ _vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
+ }
+
_sw.write(text, 216, _textColors[textColor], balloon->surface);
}
@@ -713,6 +730,8 @@ void BalloonManager_br::reset() {
}
_numBalloons = 0;
+
+ _vm->stopTextToSpeech();
}
void BalloonManager_br::cacheAnims() {
diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp
index effa08616f5..025e1698d70 100644
--- a/engines/parallaction/callables_ns.cpp
+++ b/engines/parallaction/callables_ns.cpp
@@ -413,6 +413,23 @@ void Parallaction_ns::_c_testResult(void *parm) {
_gfx->showLabel(_testResultLabels[0], CENTER_LABEL_HORIZONTAL, 38);
_gfx->showLabel(_testResultLabels[1], CENTER_LABEL_HORIZONTAL, 58);
+ const char *charName = nullptr;
+ switch (_characterVoiceID) {
+ case kDoug:
+ charName = "Doug Nuts";
+ break;
+ case kDonna:
+ charName = "Donna Fatale";
+ break;
+ case kDino:
+ charName = "Dino Fagioli";
+ break;
+ }
+
+ if (charName != nullptr) {
+ sayText(charName, Common::TextToSpeechManager::QUEUE);
+ }
+
return;
}
diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp
index 414db021127..2ba8b71f523 100644
--- a/engines/parallaction/detection.cpp
+++ b/engines/parallaction/detection.cpp
@@ -70,7 +70,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
@@ -96,7 +96,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
@@ -120,7 +120,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_NO_FLAGS,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_MULT,
@@ -135,7 +135,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformAmiga,
ADGF_DEMO,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_EN | GF_DEMO,
@@ -158,7 +158,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::IT_ITA,
Common::kPlatformAmiga,
ADGF_NO_FLAGS,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_Nippon,
GF_LANG_IT,
@@ -173,7 +173,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_UNSTABLE,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
@@ -187,7 +187,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformDOS,
ADGF_DEMO | ADGF_UNSTABLE,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_DEMO,
@@ -201,7 +201,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_UNSTABLE,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_MULT,
@@ -215,7 +215,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::UNK_LANG,
Common::kPlatformAmiga,
ADGF_DEMO | ADGF_UNSTABLE,
- GUIO1(GUIO_NOSPEECH)
+ GUIO2(GUIO_NOSPEECH, GAMEOPTION_TTS)
},
GType_BRA,
GF_LANG_EN | GF_DEMO,
diff --git a/engines/parallaction/detection.h b/engines/parallaction/detection.h
index 5b0d88b8eae..25d3b3eba13 100644
--- a/engines/parallaction/detection.h
+++ b/engines/parallaction/detection.h
@@ -49,6 +49,8 @@ struct PARALLACTIONGameDescription {
uint32 features;
};
+#define GAMEOPTION_TTS GUIO_GAMEOPTIONS1
+
} // End of namespace Parallaction
#endif // PARALLACTION_DETECTION_H
diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp
index 76367b42df5..53bdf03481c 100644
--- a/engines/parallaction/dialogue.cpp
+++ b/engines/parallaction/dialogue.cpp
@@ -49,6 +49,7 @@ struct BalloonPositions {
+static const int kNumberOfVoiceDatas = 65;
class DialogueManager {
@@ -60,6 +61,9 @@ class DialogueManager {
GfxObj *_answerer;
int _faceId;
+ int _questionerVoiceID;
+ int _answererVoiceID;
+
Question *_q;
int _answerId;
@@ -137,6 +141,23 @@ DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) {
_questioner = isNpc ? _vm->_disk->loadTalk(_z->u._filename.c_str()) : _vm->_char._talk;
_answerer = _vm->_char._talk;
+#ifdef USE_TTS
+ _answererVoiceID = _vm->_characterVoiceID;
+
+ if (!isNpc) {
+ _questionerVoiceID = _answererVoiceID;
+ } else {
+ _questionerVoiceID = kNarratorVoiceID;
+
+ for (int i = 1; i < kNumberOfVoiceDatas; ++i) {
+ if (characterVoiceDatas[i].characterName && !scumm_stricmp(characterVoiceDatas[i].characterName, _z->u._filename.c_str())) {
+ _questionerVoiceID = i;
+ break;
+ }
+ }
+ }
+#endif
+
_cmdList = nullptr;
_answerId = 0;
@@ -213,6 +234,8 @@ bool DialogueManager::testAnswerFlags(Answer *a) {
void DialogueManager::displayAnswers() {
+ _vm->setTTSVoice(_answererVoiceID);
+
// create balloons
int id;
for (int i = 0; i < _numVisAnswers; ++i) {
@@ -278,6 +301,15 @@ int16 DialogueManager::selectAnswerN() {
bool DialogueManager::displayQuestion() {
if (_q->textIsNull()) return false;
+#ifdef USE_TTS
+ // Some dialogue exchanges involve more than 1 character, differentiated by the mood
+ if (_questionerVoiceID < kNumberOfVoiceDatas - 1 && characterVoiceDatas[_questionerVoiceID + 1].characterName == nullptr) {
+ _vm->setTTSVoice(_questionerVoiceID + _q->speakerMood());
+ } else {
+ _vm->setTTSVoice(_questionerVoiceID);
+ }
+#endif
+
_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->balloonWinding(), BalloonManager::kNormalColor);
_faceId = _gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y);
_gfx->setItemFrame(_faceId, _q->speakerMood());
@@ -415,6 +447,7 @@ protected:
}
if ((_vm->_password.size() == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) {
+ _vm->sayText(_vm->_password, Common::TextToSpeechManager::INTERRUPT);
if (checkPassword()) {
return 0;
} else {
@@ -448,6 +481,8 @@ public:
if (a->_text.contains("%P")) {
_askPassword = true;
+
+ _vm->sayText(a->_text.substr(0, a->_text.find('@')), Common::TextToSpeechManager::INTERRUPT);
}
_visAnswers[_numVisAnswers]._a = a;
@@ -535,6 +570,8 @@ void Parallaction::exitDialogueMode() {
debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()");
_input->_inputMode = Input::kInputModeGame;
+ setTTSVoice(_characterVoiceID);
+
/* Since the current instance of _dialogueMan must be destroyed before the
zone commands are executed, as they may create a new instance of _dialogueMan that
would overwrite the current, we need to save the references to the command lists.
diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h
index 667f735a919..36f2dc71ee7 100644
--- a/engines/parallaction/exec.h
+++ b/engines/parallaction/exec.h
@@ -202,6 +202,8 @@ public:
class ProgramExec_ns : public ProgramExec {
protected:
+ int _currentCredit;
+
Parallaction_ns *_vm;
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp
index e5f143ab48b..d93825d78dd 100644
--- a/engines/parallaction/exec_ns.cpp
+++ b/engines/parallaction/exec_ns.cpp
@@ -24,10 +24,201 @@
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
+#include "common/config-manager.h"
#include "common/textconsole.h"
namespace Parallaction {
+#ifdef USE_TTS
+
+// Transcribed for TTS; only English is translated in-game
+static const char *openingCreditsSecondLine[] = {
+ "Grafica Totale: Max M.", // Italian
+ "Graphiques Totaux: Max M.", // French
+ "Total Graphics: Max M.", // English
+ "Gesamtgrafik: Max M." // German
+};
+
+static const char *openingCreditsThirdLine[] = {
+ "Design del Gioco: Mr. Tzutzumi", // Italian
+ "Conception de Jeux: Mr. Tzutzumi", // French
+ "Game Design: Mr. Tzutzumi", // English
+ "Spieldesign: Mr. Tzutzumi" // German
+};
+
+// Transcribed for TTS; only Italian is translated in-game
+static const char *endCreditsItalian[] = {
+ "La Cassiera",
+ "La Segretaria",
+ "Il Taxista",
+ "Il Giornalaio",
+ "Passante",
+ "Il Segretario",
+ "L'Uccello",
+ "L'Inserviente",
+ "Il Priore",
+ "Il Suicida",
+ "Il Direttore",
+ "Passante",
+ "Fratello Shinpui",
+ "Apollo il Gorilla",
+ "Il Portiere",
+ "Il Guru",
+ "Fratello Baka",
+ "Josko il Barman",
+ "Figaro L' Estetista",
+ "Il Guardiano del Museo",
+ "Passante",
+ "Il Losco Max",
+ "Mister Y",
+ "Il Punk",
+ "Passante",
+ "Il Signor Bemutsu",
+ "L' Annunciatore",
+ "La Punkina",
+ "L' Imperatore",
+ "Il Dottor Ki",
+ "Il Cuoco",
+ "Il Punk",
+ "Passante",
+ "Il Losco Figuro",
+ "Chan L'Onesto",
+ "",
+ "La Geisha",
+ "Kos 'O Professore",
+ "Il Giocatore di Pacinko"
+};
+
+static const char *endCreditsFrench[] = {
+ "La Cassi\350re",
+ "La Secr\351taire",
+ "Le Chaffeur de Taxi",
+ "Le Marchand de Journaux",
+ "La Pi\351tonne",
+ "Le Secr\351taire",
+ "La Corneille",
+ "Le Oshiya",
+ "Le Prieur",
+ "Le Candidat au Suicide",
+ "Le Directeur",
+ "La Pi\351tonne",
+ "Fr\350re Shinpui",
+ "Apollo le Garde du Corps",
+ "Le Concierge",
+ "Le Guru",
+ "Fr\350re Baka",
+ "Josko le Barman",
+ "Figaro L'esth\351ticien",
+ "Le Gardien du Mus\351e",
+ "La Pi\351tonne",
+ "Le Ombrag\351e Max",
+ "Monsieur Y",
+ "Le Punk",
+ "La Pi\351tonne",
+ "Monsieur Bemutsu",
+ "Le Annonceur",
+ "Le Punk",
+ "L'Empereur",
+ "Le Docteur Ki",
+ "Le Chef",
+ "Le Punk",
+ "Le Pi\351ton",
+ "Le Louche Personnage",
+ "Honest Chan",
+ "",
+ "La Geisha",
+ "Professeur Kos",
+ "Le Joueur de Pachinko"
+};
+
+static const char *endCreditsEnglish[] = {
+ "Cashier",
+ "Secretary",
+ "Taxi-driver",
+ "Newspaper Seller",
+ "Pedestrian",
+ "Secretary",
+ "Grackle",
+ "Oshiya",
+ "Prior",
+ "Suicidal Man",
+ "Governor",
+ "Pedestrian",
+ "Brother Shinpui",
+ "Apollo the Bodyguard",
+ "Door-keeper",
+ "Guru",
+ "Brother Baka",
+ "Josko the Barman",
+ "Figaro the Beautician",
+ "Museum Custodian",
+ "Pedestrian",
+ "Sullen Max",
+ "Mister Y",
+ "Punk",
+ "Pedestrian",
+ "Mister Bemutsu",
+ "Announcer",
+ "Punk",
+ "Emperor",
+ "Doctor Ki",
+ "Cook",
+ "Punk",
+ "Pedestrian",
+ "Shady Type",
+ "Honest Chan",
+ "",
+ "Geisha",
+ "Professor Kos",
+ "Pachinko Player"
+};
+
+static const char *endCreditsGerman[] = {
+ "Die Kassiererin",
+ "Die Sekret\344rin",
+ "Der Taxifahrer",
+ "Der Zeitungsverk\344ufer",
+ "Die Passantin",
+ "Der Sekret\344r",
+ "Des Grakula",
+ "Der Oshiya",
+ "Der Prior",
+ "Der Selbstm\366rder",
+ "Der Direktor",
+ "Die Passantin",
+ "Bruder Shinpui",
+ "Apollo der Bodyguard",
+ "Der Portier",
+ "Der Guru",
+ "Bruder Baka",
+ "Josko der Barman",
+ "Figaro, der Kosmetiker",
+ "Der Museumsw\344rter",
+ "Die Passantin",
+ "Schattig Max",
+ "Herr Y",
+ "Der Punker",
+ "Die Passantin",
+ "Herr Bemutsu",
+ "Der Ansager",
+ "Der Punker",
+ "Der Kaiser",
+ "Doktor Ki",
+ "Der Koch",
+ "Der Punker",
+ "Der Passant",
+ "Der Dunkler Typ",
+ "Honest Chan",
+ "",
+ "Die Geisha",
+ "Professor Kos",
+ "Der Pachinko-Spieler"
+};
+
+static const int kNumberOfCredits = ARRAYSIZE(endCreditsItalian);
+
+#endif
+
#define INST_ON 1
#define INST_OFF 2
#define INST_X 3
@@ -68,6 +259,16 @@ DECLARE_INSTRUCTION_OPCODE(on) {
inst->_a->_flags |= kFlagsActive;
inst->_a->_flags &= ~kFlagsRemove;
+
+#ifdef USE_TTS
+ if (scumm_stricmp(inst->_a->_name, "telo3") == 0) {
+ _vm->setTTSVoice(kNarratorVoiceID);
+ _vm->sayText(openingCreditsSecondLine[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+ } else if (scumm_stricmp(inst->_a->_name, "game") == 0) {
+ _vm->setTTSVoice(kNarratorVoiceID);
+ _vm->sayText(openingCreditsThirdLine[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+ }
+#endif
}
@@ -85,6 +286,21 @@ DECLARE_INSTRUCTION_OPCODE(loop) {
DECLARE_INSTRUCTION_OPCODE(endloop) {
+ if (ctxt._program->_loopStart == 11 && scumm_stricmp(_vm->_location._name, "test") == 0) {
+ // Delay moving on from test results until TTS is done or the player clicks,
+ // so the TTS system can speak them fully
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+ ctxt._program->_loopCounter = 2;
+ int event = _vm->_input->getLastButtonEvent();
+
+ if (event == kMouseLeftUp) {
+ ttsMan->stop();
+ ctxt._program->_loopCounter = 0;
+ }
+ }
+ }
+
if (--ctxt._program->_loopCounter > 0) {
ctxt._ip = ctxt._program->_loopStart;
}
@@ -104,6 +320,33 @@ DECLARE_INSTRUCTION_OPCODE(inc) {
int16 lvalue = inst->_opA.getValue();
if (inst->_index == INST_INC) {
+#ifdef USE_TTS
+ if (_vm->_endCredits && ctxt._anim->_name[0] == 's' && _currentCredit < kNumberOfCredits) {
+ const char **credits;
+
+ switch (_vm->getInternLanguage()) {
+ case kItalian:
+ credits = endCreditsItalian;
+ break;
+ case kFrench:
+ credits = endCreditsFrench;
+ break;
+ case kEnglish:
+ credits = endCreditsEnglish;
+ break;
+ case kGerman:
+ credits = endCreditsGerman;
+ break;
+ default:
+ credits = endCreditsItalian;
+ break;
+ }
+
+ _vm->sayText(credits[_currentCredit], Common::TextToSpeechManager::QUEUE);
+ _currentCredit++;
+ }
+#endif
+
lvalue += _si;
} else {
lvalue -= _si;
@@ -314,6 +557,8 @@ CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : CommandExec(vm), _vm(vm) {
}
ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) {
+ _currentCredit = 0;
+
_instructionNames = _instructionNamesRes_ns;
ProgramOpcodeSet *table = nullptr;
diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp
index 78013419d9a..a9b146f581e 100644
--- a/engines/parallaction/graphics.cpp
+++ b/engines/parallaction/graphics.cpp
@@ -551,6 +551,7 @@ GfxObj *Gfx::renderFloatingLabel(Font *font, char *text) {
GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel");
obj->transparentKey = LABEL_TRANSPARENT_COLOR;
obj->layer = LAYER_FOREGROUND;
+ obj->_text = text;
return obj;
}
@@ -559,6 +560,8 @@ void Gfx::showFloatingLabel(GfxObj *label) {
hideFloatingLabel();
if (label) {
+ _vm->sayText(label->_text, Common::TextToSpeechManager::INTERRUPT);
+
label->x = -1000;
label->y = -1000;
label->setFlags(kGfxObjVisible);
@@ -648,11 +651,13 @@ GfxObj *Gfx::createLabel(Font *font, const char *text, byte color) {
GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label");
obj->transparentKey = LABEL_TRANSPARENT_COLOR;
obj->layer = LAYER_FOREGROUND;
+ obj->_text = text;
+ obj->_text.replace('@', ' ');
return obj;
}
-void Gfx::showLabel(GfxObj *label, int16 x, int16 y) {
+void Gfx::showLabel(GfxObj *label, int16 x, int16 y, bool queueTTS, bool voiceText) {
if (!label) {
return;
}
@@ -673,6 +678,20 @@ void Gfx::showLabel(GfxObj *label, int16 x, int16 y) {
label->x = x;
label->y = y;
+ if (voiceText) {
+ _vm->setTTSVoice(kNarratorVoiceID);
+
+ Common::TextToSpeechManager::Action action;
+
+ if (queueTTS) {
+ action = Common::TextToSpeechManager::QUEUE;
+ } else {
+ action = Common::TextToSpeechManager::INTERRUPT;
+ }
+
+ _vm->sayText(label->_text, action);
+ }
+
_labels.push_back(label);
}
diff --git a/engines/parallaction/graphics.h b/engines/parallaction/graphics.h
index 0d639a1bb7a..35729e88039 100644
--- a/engines/parallaction/graphics.h
+++ b/engines/parallaction/graphics.h
@@ -320,6 +320,8 @@ public:
int _pathId;
bool _hasPath;
+ Common::String _text;
+
GfxObj(uint type, Frames *frames, const char *name = NULL);
virtual ~GfxObj();
@@ -457,7 +459,7 @@ public:
GfxObj *renderFloatingLabel(Font *font, char *text);
GfxObj *createLabel(Font *font, const char *text, byte color);
- void showLabel(GfxObj *label, int16 x, int16 y);
+ void showLabel(GfxObj *label, int16 x, int16 y, bool queueTTS = true, bool voiceText = true);
void hideLabel(GfxObj *label);
void freeLabels();
void unregisterLabel(GfxObj *label);
diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp
index 3bd7ff0e673..d33ee56ef12 100644
--- a/engines/parallaction/gui_ns.cpp
+++ b/engines/parallaction/gui_ns.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/system.h"
#include "common/hashmap.h"
#include "common/textconsole.h"
@@ -32,6 +33,24 @@
namespace Parallaction {
+#ifdef USE_TTS
+
+static const char *languageSelectBlockTexts[] = {
+ "Dizionario\nGiapponese\nItaliano",
+ "Dictionnaire\nJaponais\nFran\347ais",
+ "Dictionary\nJapanese\nEnglish",
+ "W\366rterbuch\nJapanese\nDeutsch"
+};
+
+static const char *ttsLanguages[] = {
+ "it",
+ "fr",
+ "en",
+ "de"
+};
+
+#endif
+
class SplashInputState_NS : public MenuInputState {
protected:
Common::String _slideName;
@@ -109,6 +128,7 @@ class ChooseLanguageInputState_NS : public MenuInputState {
Common::Rect _dosLanguageSelectBlocks[4];
Common::Rect _amigaLanguageSelectBlocks[4];
const Common::Rect *_blocks;
+ int _previousBlock;
Parallaction *_vm;
@@ -129,6 +149,8 @@ public:
_amigaLanguageSelectBlocks[2] = Common::Rect( 178, 60, 226, 130 ); // English
_amigaLanguageSelectBlocks[3] = Common::Rect( 227, 35, 275, 105 ); // German
+ _previousBlock = -1;
+
if (_vm->getPlatform() == Common::kPlatformAmiga) {
if (!(_vm->getFeatures() & GF_LANG_MULT)) {
@@ -168,15 +190,30 @@ public:
}
int event = _vm->_input->getLastButtonEvent();
- if (event != kMouseLeftUp) {
- return this;
- }
Common::Point p;
_vm->_input->getCursorPos(p);
for (uint16 i = 0; i < 4; i++) {
if (_blocks[i].contains(p)) {
+#ifdef USE_TTS
+ if (_previousBlock != i) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
+ ttsMan->setLanguage(ttsLanguages[i]);
+ }
+
+ _vm->setTTSVoice(kNarratorVoiceID);
+ _vm->sayText(languageSelectBlockTexts[i], Common::TextToSpeechManager::INTERRUPT);
+ _previousBlock = i;
+ }
+#endif
+
+ if (event != kMouseLeftUp) {
+ return this;
+ }
+
+ _vm->stopTextToSpeech();
_vm->setInternLanguage(i);
_vm->beep();
destroyLabels();
@@ -184,6 +221,8 @@ public:
}
}
+ _previousBlock = -1;
+
return this;
}
@@ -259,7 +298,7 @@ public:
_vm->_gfx->hideLabel(_labels[_oldChoice]);
if (_choice != -1)
- _vm->_gfx->showLabel(_labels[_choice], 60, 30);
+ _vm->_gfx->showLabel(_labels[_choice], 60, 30, false);
_oldChoice = _choice;
}
@@ -312,7 +351,25 @@ public:
}
};
+#ifdef USE_TTS
+
+// This text is translated for TTS. Only English is translated in-game
+static const char *ttsIntroMsg[] = {
+ // Italian
+ "Premi il tasto sinistro del mouse per vedere l'introduzione\n"
+ "Premere il tasto destro del mouse per iniziare",
+ // French
+ "Appuyez sur le bouton gauche de la souris pour voir l'intro\n"
+ "Appuyez sur le bouton droit de la souris pour d\351marrer",
+ // English
+ "Press left mouse button to see intro\n"
+ "Press right mouse button to start",
+ // German
+ "Dr\374cken Sie die linke Maustaste, um das Intro anzuzeigen\n"
+ "Zum Starten rechte Maustaste dr\374cken"
+};
+#endif
class NewGameInputState_NS : public MenuInputState {
Parallaction_ns *_vm;
@@ -338,6 +395,7 @@ public:
if (event == kMouseLeftUp || event == kMouseRightUp) {
_vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
destroyLabels();
+ _vm->stopTextToSpeech();
if (event == kMouseLeftUp) {
_vm->scheduleLocationSwitch("fogne.dough");
@@ -369,14 +427,17 @@ public:
_vm->changeBackground("test");
_vm->_input->setMouseState(MOUSE_ENABLED_HIDE);
+#ifdef USE_TTS
+ _vm->sayText(ttsIntroMsg[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+#endif
_labels[0] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[0], 1);
_labels[1] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[1], 1);
_labels[2] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[2], 1);
_labels[3] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[3], 1);
- _vm->_gfx->showLabel(_labels[0], CENTER_LABEL_HORIZONTAL, 50);
- _vm->_gfx->showLabel(_labels[1], CENTER_LABEL_HORIZONTAL, 70);
- _vm->_gfx->showLabel(_labels[2], CENTER_LABEL_HORIZONTAL, 100);
- _vm->_gfx->showLabel(_labels[3], CENTER_LABEL_HORIZONTAL, 120);
+ _vm->_gfx->showLabel(_labels[0], CENTER_LABEL_HORIZONTAL, 50, false, false);
+ _vm->_gfx->showLabel(_labels[1], CENTER_LABEL_HORIZONTAL, 70, false, false);
+ _vm->_gfx->showLabel(_labels[2], CENTER_LABEL_HORIZONTAL, 100, false, false);
+ _vm->_gfx->showLabel(_labels[3], CENTER_LABEL_HORIZONTAL, 120, false, false);
}
};
@@ -676,6 +737,46 @@ const char *SelectCharacterInputState_NS::_charStartLocation[] = {
"test.dough"
};
+#ifdef USE_TTS
+
+// Translated text for TTS. Only English is shown in-game
+static const char *italianCredits[] = {
+ "Musica e Effeti Sonori: Marco Caprelli",
+ "Versione PC: Riccardo Ballarino",
+ "Responsabile del progetto: Lovrano Canepa",
+ "Produzione: Bruno Boz",
+ "Sentiti ringraziamenti a: Luigi Benedicenti - Gilda e Danilo",
+ "Copyright 1992 Euclidea s.r.l Italia, tutti i diritti riservati"
+};
+
+static const char *frenchCredits[] = {
+ "Musique et Bruitages: Marco Caprelli",
+ "Version PC: Riccardo Ballarino",
+ "Chef de projet: Lovrano Canepa",
+ "Production: Bruno Boz",
+ "Nous tenons \340 remercier: Luigi Benedicenti - Gilda et Danilo",
+ "Copyright 1992 Euclidea s.r.l Italie, tous droits r\351serv\351s"
+};
+
+static const char *englishCredits[] = {
+ "Music and Sound Effects: Marco Caprelli",
+ "PC Version: Riccardo Ballarino",
+ "Project Manager: Lovrano Canepa",
+ "Production: Bruno Boz",
+ "Special thanks to: Luigi Benedicenti - Gilda and Danilo",
+ "Copyright 1992 Euclidea s.r.l Italy, all rights reserved"
+};
+
+static const char *germanCredits[] = {
+ "Musiken und Soundeffekte: Marco Caprelli",
+ "PC Version: Riccardo Ballarino",
+ "Projektmanager: Lovrano Canepa",
+ "Produktion: Bruno Boz",
+ "Wir danken: Luigi Benedicenti - Gilda und Danilo",
+ "Copyright 1992 Euclidea s.r.l Italien, Alle Rechte vorbehalten"
+};
+
+#endif
class ShowCreditsInputState_NS : public MenuInputState {
Parallaction *_vm;
@@ -717,8 +818,31 @@ public:
_labels[0] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._role, 1);
_labels[1] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._name, 1);
- _vm->_gfx->showLabel(_labels[0], CENTER_LABEL_HORIZONTAL, 80);
- _vm->_gfx->showLabel(_labels[1], CENTER_LABEL_HORIZONTAL, 100);
+ _vm->_gfx->showLabel(_labels[0], CENTER_LABEL_HORIZONTAL, 80, false, false);
+ _vm->_gfx->showLabel(_labels[1], CENTER_LABEL_HORIZONTAL, 100, false, false);
+
+#ifdef USE_TTS
+ const char **creditsTexts;
+ switch (_vm->getInternLanguage()) {
+ case kItalian:
+ creditsTexts = italianCredits;
+ break;
+ case kFrench:
+ creditsTexts = frenchCredits;
+ break;
+ case kEnglish:
+ creditsTexts = englishCredits;
+ break;
+ case kGerman:
+ creditsTexts = germanCredits;
+ break;
+ default:
+ creditsTexts = englishCredits;
+ break;
+ }
+
+ _vm->sayText(creditsTexts[_current], Common::TextToSpeechManager::INTERRUPT);
+#endif
}
@@ -732,7 +856,8 @@ public:
int event = _vm->_input->getLastButtonEvent();
uint32 curTime = _vm->_system->getMillis();
- if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if ((event == kMouseLeftUp) || (curTime - _startTime > 5500 && !(ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()))) {
_current++;
_startTime = curTime;
destroyLabels();
@@ -748,6 +873,7 @@ public:
}
void enter() override {
+ _vm->setTTSVoice(kNarratorVoiceID);
_current = -1;
_vm->_input->setMouseState(MOUSE_DISABLED);
}
@@ -762,6 +888,18 @@ const ShowCreditsInputState_NS::Credit ShowCreditsInputState_NS::_credits[6] = {
{"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"}
};
+#ifdef USE_TTS
+
+// Only English is translated in-game
+static const char *clickMousePrompts[] = {
+ "Fare clic sul pulsante del mouse per iniziare", // Italian
+ "Cliquez sur le bouton de la souris pour d\351marrer", // French
+ "Click mouse button to start", // English
+ "Klicken Sie mit der Maustaste, um zu starten" // German
+};
+
+#endif
+
class EndIntroInputState_NS : public MenuInputState {
Parallaction_ns *_vm;
bool _isDemo;
@@ -806,7 +944,10 @@ public:
if (!_isDemo) {
_vm->_soundManI->stopMusic();
_label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1);
- _vm->_gfx->showLabel(_label, CENTER_LABEL_HORIZONTAL, 80);
+#ifdef USE_TTS
+ _vm->sayText(clickMousePrompts[_vm->getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+#endif
+ _vm->_gfx->showLabel(_label, CENTER_LABEL_HORIZONTAL, 80, false, false);
}
}
};
diff --git a/engines/parallaction/metaengine.cpp b/engines/parallaction/metaengine.cpp
index 04d9b7c07f0..70adce018b2 100644
--- a/engines/parallaction/metaengine.cpp
+++ b/engines/parallaction/metaengine.cpp
@@ -43,12 +43,38 @@ Common::Platform Parallaction::getPlatform() const { return _gameDescription->de
} // End of namespace Parallaction
+#ifdef USE_TTS
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ {
+ GAMEOPTION_TTS,
+ {
+ _s("Enable Text to Speech"),
+ _s("Use TTS to read the descriptions (if TTS is available)"),
+ "tts_enabled",
+ false,
+ 0,
+ 0
+ }
+ },
+
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+#endif
+
class ParallactionMetaEngine : public AdvancedMetaEngine<Parallaction::PARALLACTIONGameDescription> {
public:
const char *getName() const override {
return "parallaction";
}
+#ifdef USE_TTS
+ const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
+ return optionsList;
+ }
+#endif
+
bool hasFeature(MetaEngineFeature f) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const Parallaction::PARALLACTIONGameDescription *desc) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp
index 2700ee47458..9d8a504d0d0 100644
--- a/engines/parallaction/parallaction.cpp
+++ b/engines/parallaction/parallaction.cpp
@@ -19,9 +19,11 @@
*
*/
+#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/system.h"
#include "common/textconsole.h"
+#include "common/text-to-speech.h"
#include "parallaction/exec.h"
#include "parallaction/input.h"
@@ -76,6 +78,7 @@ Parallaction::Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gam
_currentLocationIndex = 0;
_numLocations = 0;
_language = 0;
+ _characterVoiceID = 0;
}
Parallaction::~Parallaction() {
@@ -116,6 +119,19 @@ Common::Error Parallaction::init() {
_location._followerStartFrame = 0;
_objects = nullptr;
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr) {
+ ttsMan->enable(ConfMan.getBool("tts_enabled"));
+
+ // For the multilingual versions, start with English TTS
+ // The language will be switched later after the user picks their language
+ if (getLanguage() != Common::UNK_LANG) {
+ ttsMan->setLanguage(ConfMan.get("language"));
+ } else {
+ ttsMan->setLanguage("en");
+ }
+ }
+
_screenSize = _screenWidth * _screenHeight;
Common::strcpy_s(_characterName1, "null");
@@ -254,6 +270,8 @@ void Parallaction::showSlide(const char *name, int x, int y) {
}
void Parallaction::showLocationComment(const Common::String &text, bool end) {
+ setTTSVoice(kNarratorVoiceID);
+ sayText(text, Common::TextToSpeechManager::INTERRUPT);
_balloonMan->setLocationBalloon(text, end);
}
@@ -346,6 +364,7 @@ void Parallaction::doLocationEnterTransition() {
_input->waitForButtonEvent(kMouseLeftUp);
_gfx->freeDialogueObjects();
+ setTTSVoice(_characterVoiceID);
// fades maximum intensity palette towards approximation of main palette
for (uint16 _si = 0; _si<6; _si++) {
@@ -792,6 +811,59 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) {
return ZonePtr();
}
+void Parallaction::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
+ ttsMan->say(text, action, Common::CodePage::kWindows1252);
+ }
+}
+
+void Parallaction::setTTSVoice(int id) const {
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && _gameType == GType_Nippon) {
+ Common::Array<int> voices;
+ int pitch = 0;
+ Common::TTSVoice::Gender gender;
+
+ if (characterVoiceDatas[id].male) {
+ voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
+ gender = Common::TTSVoice::MALE;
+ } else {
+ voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+ gender = Common::TTSVoice::FEMALE;
+ }
+
+ // If no voice is available for the necessary gender, set the voice to default
+ if (voices.empty()) {
+ ttsMan->setVoice(0);
+ } else {
+ int voiceIndex = characterVoiceDatas[id].voiceID % voices.size();
+ ttsMan->setVoice(voices[voiceIndex]);
+ }
+
+ // If no voices are available for this gender, alter the pitch to mimic a voice
+ // of the other gender
+ if (ttsMan->getVoice().getGender() != gender) {
+ if (gender == Common::TTSVoice::MALE) {
+ pitch -= 50;
+ } else {
+ pitch += 50;
+ }
+ }
+
+ ttsMan->setPitch(pitch);
+ }
+#endif
+}
+
+void Parallaction::stopTextToSpeech() const {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+ ttsMan->stop();
+ }
+}
+
ZonePtr Location::findZone(const char *name) {
for (ZoneList::iterator it = _zones.begin(); it != _zones.end(); ++it) {
if (!scumm_stricmp((*it)->_name, name)) return *it;
diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h
index 1cd620dae33..abfeacc9310 100644
--- a/engines/parallaction/parallaction.h
+++ b/engines/parallaction/parallaction.h
@@ -29,6 +29,7 @@
#include "common/random.h"
#include "common/savefile.h"
#include "common/textconsole.h"
+#include "common/text-to-speech.h"
#include "engines/engine.h"
@@ -84,6 +85,12 @@ enum {
kEvIngameMenu = 8000
};
+enum LanguageIndex {
+ kItalian = 0,
+ kFrench = 1,
+ kEnglish = 2,
+ kGerman = 3
+};
@@ -219,6 +226,92 @@ public:
bool dummy() const;
};
+#ifdef USE_TTS
+
+struct CharacterVoiceData {
+ const char *characterName;
+ uint8 voiceID;
+ bool male;
+};
+
+static const CharacterVoiceData characterVoiceDatas[] = {
+ { nullptr, 0, true }, // Used as the narrator for cutscene text
+ { "dough", 1, true },
+ { "donna", 0, false },
+ { "dino", 2, true },
+ { "drki", 3, true },
+ { "ddd.talk", 1, true },
+ { nullptr, 0, false }, // Donna in ddd.talk
+ { nullptr, 2, true }, // Dino in ddd.talk
+ { "police.talk", 4, true },
+ { "vecchio.talk", 5, true },
+ { "karaoke.talk", 6, true },
+ { "suicida.talk", 7, true },
+ { "direttore.talk", 8, true },
+ { "guardiano.talk", 9, true },
+ { "sento.talk", 10, true },
+ { "giocatore.talk", 11, true },
+ { "mona.talk", 12, true },
+ { "monaco2.talk", 13, true },
+ { "uccello.talk", 14, true },
+ { "guru.talk", 15, true },
+ { "monaco1.talk", 16, true },
+ { "segretario.talk", 17, true },
+ { "imperatore.talk", 18, true },
+ { "secondo.talk", 19, true },
+ { "taxista.talk", 20, true },
+ { "bemutsu.talk", 21, true },
+ { "negro.talk", 22, true },
+ { "punk.talk", 23, true },
+ { "chan.talk", 24, true },
+ { "donna0.talk", 0, false },
+ { "maxkos.talk", 25, true },
+ { nullptr, 26, true }, // Second person in the maxkos.talk dialogue
+ { "drki.talk", 3, true },
+ { "barman.talk", 27, true },
+ { "losco.talk", 28, true },
+ { "mitsu.talk", 29, true },
+ { "autoradio.talk", 30, true },
+ { "passa1.talk", 31, true },
+ { "passa2.talk", 1, false },
+ { "passa3.talk", 32, true },
+ { "giornalaio.talk", 33, true },
+ { "pazza1.talk", 2, false },
+ { "pazza2.talk", 3, false },
+ { "pazza3.talk", 4, false },
+ { "citofono.talk", 34, true },
+ { "perdelook.talk", 0, false },
+ { "tele.talk", 35, true },
+ { "dough.talk", 1, true },
+ { "donna.talk", 0, false },
+ { "guanti.talk", 36, true },
+ { "cuoco.talk", 37, true },
+ { "punks.talk", 38, true },
+ { nullptr, 39, true }, // Second person in the punks.talk dialogue
+ { "punko.talk", 40, true },
+ { "hotdog.talk", 41, true },
+ { "cameriera.talk", 5, false },
+ { "rice1.talk", 42, true },
+ { nullptr, 43, true }, // Second person in the rice1.talk dialogue
+ { "segretaria.talk", 6, false },
+ { "robot.talk", 44, true },
+ { "portiere.talk", 45, true },
+ { "figaro.talk", 46, true },
+ { "ominos.talk", 47, true },
+ { "cassiera.talk", 7, false },
+ { "dino.talk", 2, true }
+};
+
+#endif
+
+enum PlayableCharacterVoiceID {
+ kDoug = 1,
+ kDonna = 2,
+ kDino = 3,
+ kDrki = 4
+};
+
+static const int kNarratorVoiceID = 0;
class SaveLoad;
@@ -301,6 +394,7 @@ public:
Location _location;
ZonePtr _activeZone;
char _characterName1[50]; // only used in changeCharacter
+ int _characterVoiceID;
ZonePtr _zoneTrap;
ZonePtr _commentZone;
Common::String _newLocationName;
@@ -359,6 +453,9 @@ public:
int16 getInventoryItemIndex(int16 pos);
void openInventory();
void closeInventory();
+ void sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const;
+ void setTTSVoice(int id) const;
+ void stopTextToSpeech() const;
virtual void parseLocation(const char* name) = 0;
virtual void changeLocation() = 0;
@@ -389,6 +486,8 @@ public:
uint16 _score;
Common::String _password;
+ bool _endCredits;
+
public:
void parseLocation(const char *filename) override;
diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp
index 6c2224df329..053b3f968ed 100644
--- a/engines/parallaction/parallaction_ns.cpp
+++ b/engines/parallaction/parallaction_ns.cpp
@@ -34,6 +34,43 @@
namespace Parallaction {
+#ifdef USE_TTS
+
+static const char *openingCreditsFirstLine[] = {
+ "Programmatore Assoluto: Paolo Costabel", // Italian
+ "Programmeur Absolu: Paolo Costabel", // French
+ "Absolute Programmer: Paolo Costabel", // English
+ "Absoluter Programmierer: Paolo Costabel" // German
+};
+
+// Transcribed for TTS
+// First four names of the end credits are best voiced as one block; the remaining credits
+// are voiced one at a time elsewhere
+static const char *endCreditsFirstSection[] = {
+ // Italian
+ "Il Dottor Buoz\n"
+ "Lo Studente\n"
+ "Il Poliziotto\n"
+ "Secondo il Secondino",
+ // French
+ "Le Professeur Buoz\n"
+ "Le \311tudiant\n"
+ "Le Police\n"
+ "Secondo le Garde",
+ // English
+ "Doctor Buoz\n"
+ "Student\n"
+ "Police\n"
+ "Secondo the Warder",
+ // German
+ "Professor Buoz\n"
+ "Der Student\n"
+ "Die Polizei\n"
+ "Secondo der W\344chter"
+};
+
+#endif
+
#define INITIAL_FREE_SARCOPHAGUS_SLOT_X 200
@@ -194,6 +231,7 @@ Common::Error Parallaction_ns::init() {
_intro = false;
_inTestResult = false;
+ _endCredits = false;
_location._animations.push_front(_char._ani);
@@ -382,6 +420,10 @@ void Parallaction_ns::changeLocation() {
_input->waitForButtonEvent(kMouseLeftUp);
_gfx->unregisterLabel(label);
delete label;
+
+ if (!locname.hasCharacter()) {
+ setTTSVoice(_characterVoiceID);
+ }
}
if (locname.hasCharacter()) {
@@ -423,7 +465,21 @@ void Parallaction_ns::changeLocation() {
// cave at the end of the game. Fix it here.
if (!strcmp(_location._name, "ingressocav"))
_input->setMouseState(MOUSE_ENABLED_SHOW);
+#ifdef USE_TTS
+ else if (!strcmp(_location._name, "final")) { // End credits
+ setTTSVoice(kNarratorVoiceID);
+ sayText(endCreditsFirstSection[getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+ _endCredits = true;
+ }
+#endif
}
+
+#ifdef USE_TTS
+ if (!strcmp(_location._name, "title1")) { // First screen of opening credits
+ setTTSVoice(kNarratorVoiceID);
+ sayText(openingCreditsFirstLine[getInternLanguage()], Common::TextToSpeechManager::INTERRUPT);
+ }
+#endif
debugC(1, kDebugExec, "changeLocation() done");
_newLocationName.clear();
@@ -472,6 +528,18 @@ void Parallaction_ns::changeCharacter(const char *name) {
_char._ani->gfxobj = _gfx->loadCharacterAnim(_char.getFullName());
+ if (scumm_stricmp(_char.getBaseName(), g_doughName) == 0) {
+ _characterVoiceID = kDoug;
+ } else if (scumm_stricmp(_char.getBaseName(), g_donnaName) == 0) {
+ _characterVoiceID = kDonna;
+ } else if (scumm_stricmp(_char.getBaseName(), g_dinoName) == 0) {
+ _characterVoiceID = kDino;
+ } else {
+ _characterVoiceID = kDrki;
+ }
+
+ setTTSVoice(_characterVoiceID);
+
if (!_char.dummy()) {
_char._head = _disk->loadHead(_char.getBaseName());
_char._talk = _disk->loadTalk(_char.getBaseName());
More information about the Scummvm-git-logs
mailing list