scummvm/engines/sherlock/talk.cpp

1220 lines
32 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "sherlock/talk.h"
#include "sherlock/sherlock.h"
#include "sherlock/screen.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_talk.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_people.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_talk.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
namespace Sherlock {
SequenceEntry::SequenceEntry() {
_objNum = 0;
_obj = nullptr;
_seqStack = 0;
_seqTo = 0;
_sequenceNumber = _frameNumber = 0;
_seqCounter = _seqCounter2 = 0;
}
/*----------------------------------------------------------------*/
void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
int length;
length = s.readUint16LE();
for (int idx = 0; idx < length - 1; ++idx)
_statement += (char)s.readByte();
s.readByte(); // Null ending
length = s.readUint16LE();
for (int idx = 0; idx < length - 1; ++idx)
_reply += (char)s.readByte();
s.readByte(); // Null ending
length = s.readUint16LE();
for (int idx = 0; idx < length - 1; ++idx)
_linkFile += (char)s.readByte();
s.readByte(); // Null ending
length = s.readUint16LE();
for (int idx = 0; idx < length - 1; ++idx)
_voiceFile += (char)s.readByte();
s.readByte(); // Null ending
_required.resize(s.readByte());
_modified.resize(s.readByte());
// Read in flag required/modified data
for (uint idx = 0; idx < _required.size(); ++idx)
_required[idx] = s.readSint16LE();
for (uint idx = 0; idx < _modified.size(); ++idx)
_modified[idx] = s.readSint16LE();
_portraitSide = s.readByte();
_quotient = s.readUint16LE();
_journal = isRoseTattoo ? s.readByte() : 0;
}
/*----------------------------------------------------------------*/
TalkHistoryEntry::TalkHistoryEntry() {
Common::fill(&_data[0], &_data[16], false);
}
/*----------------------------------------------------------------*/
Talk *Talk::init(SherlockEngine *vm) {
if (vm->getGameID() == GType_SerratedScalpel)
return new Scalpel::ScalpelTalk(vm);
else
return new Tattoo::TattooTalk(vm);
}
Talk::Talk(SherlockEngine *vm) : _vm(vm) {
_talkCounter = 0;
_talkToAbort = false;
_openTalkWindow = false;
_speaker = 0;
_talkIndex = 0;
_talkTo = 0;
_scriptSelect = 0;
_converseNum = -1;
_talkStealth = 0;
_talkToFlag = -1;
_moreTalkDown = _moreTalkUp = false;
_scriptMoreFlag = 0;
_scriptSaveIndex = -1;
_opcodes = nullptr;
_opcodeTable = nullptr;
_3doSpeechIndex = -1;
_charCount = 0;
_line = 0;
_yp = 0;
_wait = 0;
_pauseFlag = false;
_seqCount = 0;
_scriptStart = _scriptEnd = nullptr;
_endStr = _noTextYet = false;
_talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500);
}
void Talk::talkTo(const Common::String filename) {
Events &events = *_vm->_events;
Inventory &inv = *_vm->_inventory;
Journal &journal = *_vm->_journal;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
Common::Rect savedBounds = screen.getDisplayBounds();
bool abortFlag = false;
if (filename.empty())
// No filename passed, so exit
return;
// If there any canimations currently running, or a portrait is being cleared,
// save the filename for later executing when the canimation is done
bool ongoingAnim = scene._canimShapes.size() > 0;
if (IS_ROSE_TATTOO) {
ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active();
}
if (ongoingAnim || people._clearingThePortrait) {
// Make sure we're not in the middle of a script
if (!_scriptMoreFlag) {
_scriptName = filename;
_scriptSaveIndex = 0;
// Flag the selection, since we don't yet know which statement yet
_scriptSelect = 100;
_scriptMoreFlag = 3;
}
return;
}
// Save the ui mode temporarily and switch to talk mode
MenuMode savedMode = ui._menuMode;
ui._menuMode = TALK_MODE;
// Turn on the Exit option
ui._endKeyActive = true;
if (people[HOLMES]._walkCount || (!people[HOLMES]._walkTo.empty() &&
(IS_SERRATED_SCALPEL || people._allowWalkAbort))) {
// Only interrupt if trying to do an action, and not just if player is walking around the scene
if (people._allowWalkAbort)
abortFlag = true;
people[HOLMES].gotoStand();
}
if (_talkToAbort)
return;
freeTalkVars();
// If any sequences have changed in the prior talk file, restore them
if (_savedSequences.size() > 0) {
for (uint idx = 0; idx < _savedSequences.size(); ++idx) {
SequenceEntry &ss = _savedSequences[idx];
for (uint idx2 = 0; idx2 < ss._sequences.size(); ++idx2)
scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2];
// Reset the object's frame to the beginning of the sequence
scene._bgShapes[ss._objNum]._frameNumber = 0;
}
}
if (IS_ROSE_TATTOO) {
pullSequence();
} else {
while (!isSequencesEmpty())
pullSequence();
}
if (IS_SERRATED_SCALPEL) {
// Restore any pressed button
if (!ui._windowOpen && savedMode != STD_MODE)
static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1));
} else {
static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
}
// Clear the ui counter so that anything displayed on the info line
// before the window was opened isn't cleared
ui._menuCounter = 0;
// Close any previous window before starting the talk
if (ui._windowOpen) {
switch (savedMode) {
case LOOK_MODE:
events.setCursor(ARROW);
if (ui._invLookFlag) {
screen.resetDisplayBounds();
ui.drawInterface(2);
}
ui.banishWindow();
ui._windowBounds.top = CONTROLS_Y1;
ui._temp = ui._oldTemp = ui._lookHelp = 0;
ui._menuMode = STD_MODE;
events._pressed = events._released = events._oldButtons = 0;
ui._invLookFlag = false;
break;
case TALK_MODE:
if (_speaker < SPEAKER_REMOVE)
people.clearTalking();
if (_talkCounter)
return;
// If we were in inventory mode looking at an object, restore the
// back buffers before closing the window, so we get the ui restored
// rather than the inventory again
if (ui._invLookFlag) {
screen.resetDisplayBounds();
ui.drawInterface(2);
ui._invLookFlag = ui._lookScriptFlag = false;
}
ui.banishWindow();
ui._windowBounds.top = CONTROLS_Y1;
abortFlag = true;
break;
case INV_MODE:
case USE_MODE:
case GIVE_MODE:
inv.freeInv();
if (ui._invLookFlag) {
screen.resetDisplayBounds();
ui.drawInterface(2);
ui._invLookFlag = ui._lookScriptFlag = false;
}
ui._infoFlag = true;
ui.clearInfo();
ui.banishWindow(false);
ui._key = -1;
break;
case FILES_MODE:
ui.banishWindow(true);
ui._windowBounds.top = CONTROLS_Y1;
abortFlag = true;
break;
case SETUP_MODE:
ui.banishWindow(true);
ui._windowBounds.top = CONTROLS_Y1;
ui._temp = ui._oldTemp = ui._lookHelp = ui._invLookFlag = false;
ui._menuMode = STD_MODE;
events._pressed = events._released = events._oldButtons = 0;
abortFlag = true;
break;
default:
break;
}
}
screen.resetDisplayBounds();
events._pressed = events._released = false;
loadTalkFile(filename);
ui._selector = ui._oldSelector = ui._key = ui._oldKey = -1;
// Find the first statement that has the correct flags
int select = -1;
for (uint idx = 0; idx < _statements.size() && select == -1; ++idx) {
if (_statements[idx]._talkMap == 0)
select = _talkIndex = idx;
}
// If there's a pending automatic selection to be made, then use it
if (_scriptMoreFlag && _scriptSelect != 100)
select = _scriptSelect;
if (select == -1) {
if (IS_ROSE_TATTOO) {
static_cast<Tattoo::TattooUserInterface *>(&ui)->putMessage(
"%s", _vm->_fixedText->getText(Tattoo::kFixedText_NoEffect));
return;
}
error("Couldn't find statement to display");
}
// Add the statement into the journal and talk history
if (_talkTo != -1 && !_talkHistory[_converseNum][select])
journal.record(_converseNum, select, true);
_talkHistory[_converseNum][select] = true;
// Check if the talk file is meant to be a non-seen comment
if (filename.size() < 8 || filename[7] != '*') {
// Should we start in stealth mode?
if (_statements[select]._statement.hasPrefix("^")) {
_talkStealth = 2;
} else {
// Not in stealth mode, so bring up the ui window
_talkStealth = 0;
++_talkToFlag;
events.setCursor(WAIT);
ui._windowBounds.top = CONTROLS_Y;
ui._infoFlag = true;
ui.clearInfo();
}
// Handle replies until there's no further linked file,
// or the link file isn't a reply first cnversation
while (!_vm->shouldQuit()) {
clearSequences();
_scriptSelect = select;
_speaker = _talkTo;
// Set up the talk file extension
if (IS_ROSE_TATTOO && sound._speechOn && _scriptMoreFlag != 1)
sound._talkSoundFile += Common::String::format("%02dB", select + 1);
// Make a copy of the statement (in case the script frees the statement list), and then execute it
Statement statement = _statements[select];
doScript(_statements[select]._reply);
if (IS_ROSE_TATTOO) {
for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
people[idx]._misc = 0;
}
if (_talkToAbort)
return;
if (!_talkStealth)
ui.clearWindow();
if (statement._modified.size() > 0) {
for (uint idx = 0; idx < statement._modified.size(); ++idx)
_vm->setFlags(statement._modified[idx]);
setTalkMap();
}
// Check for a linked file
if (!statement._linkFile.empty() && !_scriptMoreFlag) {
Common::String linkFilename = statement._linkFile;
freeTalkVars();
loadTalkFile(linkFilename);
// Scan for the first valid statement in the newly loaded file
select = -1;
for (uint idx = 0; idx < _statements.size(); ++idx) {
if (_statements[idx]._talkMap == 0) {
select = idx;
break;
}
}
if (_talkToFlag == 1)
pullSequence();
// Set the stealth mode for the new talk file
Statement &newStatement = _statements[select];
_talkStealth = newStatement._statement.hasPrefix("^") ? 2 : 0;
// If the new conversion is a reply first, then we don't need
// to display any choices, since the reply needs to be shown
if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) {
_talkIndex = select;
showTalk();
// Break out of loop now that we're waiting for player input
events.setCursor(ARROW);
break;
} else {
// Add the statement into the journal and talk history
if (_talkTo != -1 && !_talkHistory[_converseNum][select])
journal.record(_converseNum, select, true);
_talkHistory[_converseNum][select] = true;
}
ui._key = ui._oldKey = 'T'; // FIXME: I'm not sure what to do here, I need ScalpelUI->_hotkeyTalk
ui._temp = ui._oldTemp = 0;
ui._menuMode = TALK_MODE;
_talkToFlag = 2;
} else {
freeTalkVars();
if (IS_SERRATED_SCALPEL) {
if (!ui._lookScriptFlag) {
ui.drawInterface(2);
ui._menuMode = STD_MODE;
ui._windowBounds.top = CONTROLS_Y1;
}
} else {
ui._menuMode = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_labTableScene ? LAB_MODE : STD_MODE;
}
ui.banishWindow();
break;
}
}
}
_talkStealth = 0;
events._pressed = events._released = events._oldButtons = 0;
events.clearKeyboard();
if (savedBounds.bottom == SHERLOCK_SCREEN_HEIGHT)
screen.resetDisplayBounds();
else
screen.setDisplayBounds(savedBounds);
_talkToAbort = abortFlag;
// If a script was added to the script stack, restore state so that the
// previous script can continue
popStack();
events.setCursor(ARROW);
}
void Talk::initTalk(int objNum) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
UserInterface &ui = *_vm->_ui;
ui._windowBounds.top = CONTROLS_Y;
ui._infoFlag = true;
_speaker = SPEAKER_REMOVE;
Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name;
loadTalkFile(talkFilename);
// Find the first statement with the correct flags
int select = -1;
for (uint idx = 0; idx < _statements.size(); ++idx) {
if (_statements[idx]._talkMap == 0) {
select = idx;
break;
}
}
if (select == -1) {
freeTalkVars();
if (!scumm_strnicmp(talkFilename.c_str(), "PATH", 4))
error("No entries found to execute in path file");
nothingToSay();
return;
}
// See if the statement is a stealth mode reply
Statement &statement = _statements[select];
if (statement._statement.hasPrefix("^")) {
clearSequences();
// Start talk in stealth mode
_talkStealth = 2;
talkTo(talkFilename);
} else if (statement._statement.hasPrefix("*")) {
// Character being spoken to will speak first
if (objNum > 1000) {
(*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
} else {
Object &obj = scene._bgShapes[objNum];
clearSequences();
pushSequence(_talkTo);
people.setListenSequence(_talkTo, 129);
events.setCursor(WAIT);
if (obj._lookPosition.y != 0)
// Need to walk to character first
people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
events.setCursor(ARROW);
}
if (!_talkToAbort)
talkTo(talkFilename);
} else {
// Holmes will be speaking first
_talkToFlag = false;
if (objNum > 1000) {
(*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
} else {
Object &obj = scene._bgShapes[objNum];
clearSequences();
pushSequence(_talkTo);
people.setListenSequence(_talkTo, 129);
events.setCursor(WAIT);
if (obj._lookPosition.y != 0)
// Walk over to person to talk to
people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
events.setCursor(ARROW);
}
if (!_talkToAbort) {
// See if walking over triggered a conversation
if (_talkToFlag) {
if (_talkToFlag == 1) {
events.setCursor(ARROW);
// _sequenceStack._count = 1;
pullSequence();
}
} else {
_talkIndex = select;
showTalk();
// Break out of loop now that we're waiting for player input
events.setCursor(ARROW);
}
_talkToFlag = -1;
}
}
}
void Talk::freeTalkVars() {
_statements.clear();
}
void Talk::loadTalkFile(const Common::String &filename) {
People &people = *_vm->_people;
Resources &res = *_vm->_res;
Sound &sound = *_vm->_sound;
// Save a copy of the talk filename
_scriptName = filename;
// Check for an existing person being talked to
_talkTo = -1;
for (int idx = 0; idx < (int)people._characters.size(); ++idx) {
if (!scumm_strnicmp(filename.c_str(), people._characters[idx]._portrait, 4)) {
_talkTo = idx;
break;
}
}
const char *chP = strchr(filename.c_str(), '.');
Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" :
Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk";
// Create the base of the sound filename used for talking in Rose Tattoo
if (IS_ROSE_TATTOO && _scriptMoreFlag != 1)
sound._talkSoundFile = Common::String(filename.c_str(), filename.c_str() + 7) + ".";
// Open the talk file for reading
Common::SeekableReadStream *talkStream = res.load(talkFile);
_converseNum = res.resourceIndex();
talkStream->skip(2); // Skip talk file version num
_statements.clear();
_statements.resize(talkStream->readByte());
for (uint idx = 0; idx < _statements.size(); ++idx)
_statements[idx].load(*talkStream, IS_ROSE_TATTOO);
delete talkStream;
if (!sound._voices)
stripVoiceCommands();
setTalkMap();
}
void Talk::stripVoiceCommands() {
for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
Statement &statement = _statements[sIdx];
// Scan for an sound effect byte, which indicates to play a sound
for (uint idx = 0; idx < statement._reply.size(); ++idx) {
if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) {
// Replace instruction character with a space, and delete the
// rest of the name following it
statement._reply = Common::String(statement._reply.c_str(),
statement._reply.c_str() + idx) + " " +
Common::String(statement._reply.c_str() + idx + 9);
}
}
// Ensure the last character of the reply is not a space from the prior
// conversion loop, to avoid any issues with the space ever causing a page
// wrap, and ending up displaying another empty page
while (statement._reply.lastChar() == ' ')
statement._reply.deleteLastChar();
}
}
void Talk::setTalkMap() {
int statementNum = 0;
for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
Statement &statement = _statements[sIdx];
// Set up talk map entry for the statement
bool valid = true;
for (uint idx = 0; idx < statement._required.size(); ++idx) {
if (!_vm->readFlags(statement._required[idx]))
valid = false;
}
statement._talkMap = valid ? statementNum++ : -1;
}
}
void Talk::pushSequence(int speaker) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
// Only proceed if a speaker is specified
if (speaker != -1) {
int objNum = people.findSpeaker(speaker);
if (objNum != -1)
pushSequenceEntry(&scene._bgShapes[objNum]);
}
}
void Talk::doScript(const Common::String &script) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Screen &screen = *_vm->_screen;
UserInterface &ui = *_vm->_ui;
_savedSequences.clear();
_scriptStart = (const byte *)script.c_str();
_scriptEnd = _scriptStart + script.size();
const byte *str = _scriptStart;
_charCount = 0;
_line = 0;
_wait = 0;
_pauseFlag = false;
_seqCount = 0;
_noTextYet = true;
_endStr = false;
_openTalkWindow = false;
if (IS_SERRATED_SCALPEL)
_yp = CONTROLS_Y + 12;
else
_yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11;
if (IS_ROSE_TATTOO) {
for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx];
p._savedNpcSequence = p._sequenceNumber;
p._savedNpcFrame = p._frameNumber;
p._resetNPCPath = true;
}
}
if (_scriptMoreFlag) {
_scriptMoreFlag = 0;
str = _scriptStart + _scriptSaveIndex;
}
// Check if the script begins with a Stealh Mode Active command
if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) {
_talkStealth = 2;
_speaker |= SPEAKER_REMOVE;
} else {
if (IS_SERRATED_SCALPEL)
pushSequence(_speaker);
if (IS_SERRATED_SCALPEL || ui._windowOpen)
ui.clearWindow();
// Need to switch speakers?
if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) {
_speaker = str[1] - 1;
if (IS_SERRATED_SCALPEL) {
str += 2;
pullSequence();
pushSequence(_speaker);
} else {
str += 3;
}
people.setTalkSequence(_speaker);
} else {
people.setTalkSequence(_speaker);
}
if (IS_SERRATED_SCALPEL) {
// Assign portrait location?
if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) {
switch (str[1] & 15) {
case 1:
people._portraitSide = 20;
break;
case 2:
people._portraitSide = 220;
break;
case 3:
people._portraitSide = 120;
break;
default:
break;
}
if (str[1] > 15)
people._speakerFlip = true;
str += 2;
}
if (IS_SERRATED_SCALPEL) {
// Remove portrait?
if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) {
_speaker = -1;
} else {
// Nope, so set the first speaker
((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker);
}
}
}
}
do {
Common::String tempString;
_wait = 0;
byte c = str[0];
if (!c) {
_endStr = true;
} else if (c == '{') {
// Start of comment, so skip over it
while (*str++ != '}')
;
} else if (isOpcode(c)) {
// the original interpreter checked for c being >= 0x80
// and if that is the case, it tried to process it as opcode, BUT ALSO ALWAYS skipped over it
// This was done inside the Spanish + German interpreters of Serrated Scalpel, not the original
// English interpreter (reverse engineered from the binaries).
//
// This resulted in special characters not getting shown in case they occurred at the start
// of sentences like for example the inverted exclamation mark and the inverted question mark.
// For further study see fonts.cpp
//
// We create an inverted exclamation mark for the Spanish version and we show it.
//
// Us not skipping over those characters may result in an assert() happening inside fonts.cpp
// in case more invalid characters exist.
// More information see bug #6931
//
// Handle control code
switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) {
case RET_EXIT:
return;
case RET_CONTINUE:
continue;
default:
break;
}
++str;
} else {
// Handle drawing the talk interface with the text
talkInterface(str);
}
// Open window if it wasn't already open, and text has already been printed
if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) {
if (!ui._slideWindows) {
screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
} else {
ui.summonWindow();
}
ui._windowOpen = true;
_openTalkWindow = false;
}
if (_wait)
// Handling pausing
talkWait(str);
} while (!_vm->shouldQuit() && !_endStr);
if (_wait != -1) {
for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) {
SequenceEntry &seq = _savedSequences[ssIndex];
Object &object = scene._bgShapes[seq._objNum];
for (uint idx = 0; idx < seq._sequences.size(); ++idx)
object._sequences[idx] = seq._sequences[idx];
object._frameNumber = seq._frameNumber;
object._seqTo = seq._seqTo;
}
pullSequence();
if (IS_SERRATED_SCALPEL) {
if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
people.clearTalking();
} else {
static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
}
}
}
int Talk::waitForMore(int delay) {
Events &events = *_vm->_events;
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
Sound &sound = *_vm->_sound;
UserInterface &ui = *_vm->_ui;
CursorId oldCursor = events.getCursor();
int key2 = 254;
bool playingSpeech = false;
// Unless we're in stealth mode, show the appropriate cursor
if (!_talkStealth) {
events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW);
}
// Handle playing any speech associated with the text being displayed
switchSpeaker();
if (sound._speechOn && IS_ROSE_TATTOO) {
sound.playSpeech(sound._talkSoundFile);
sound._talkSoundFile.setChar(sound._talkSoundFile.lastChar() + 1, sound._talkSoundFile.size() - 1);
}
playingSpeech = sound.isSpeechPlaying();
do {
if (IS_SERRATED_SCALPEL && playingSpeech && !sound.isSpeechPlaying())
people._portrait._frameNumber = -1;
scene.doBgAnim();
// If talkTo call was done via doBgAnim, abort out of talk quietly
if (_talkToAbort) {
key2 = -1;
events._released = true;
} else {
// See if there's been a button press
events.pollEventsAndWait();
events.setButtonState();
if (events.kbHit()) {
Common::KeyState keyState = events.getKey();
if (keyState.keycode == Common::KEYCODE_ESCAPE) {
if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) {
// Skip out of the introduction
_vm->setFlags(-76);
_vm->setFlags(396);
scene._goToScene = 1;
}
break;
} else if (Common::isPrint(keyState.ascii))
key2 = keyState.keycode;
}
if (_talkStealth) {
key2 = 254;
events._released = false;
}
}
// Count down the delay
if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth)
--delay;
if (playingSpeech && !sound.isSpeechPlaying())
delay = 0;
} while (!_vm->shouldQuit() && key2 == 254 && (delay || (playingSpeech && sound.isSpeechPlaying()))
&& !events._released && !events._rightReleased);
// If voices was set 2 to indicate a Scalpel voice file was playing, then reset it back to 1
if (sound._voices == 2)
sound._voices = 1;
if (delay > 0 && sound.isSpeechPlaying())
sound.stopSpeech();
// Adjust _talkStealth mode:
// mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0
// mode 3 - It was set by a pause with stealth being on before the pause, to set it to active
// mode 0/2 (Inactive/active) No change
switch (_talkStealth) {
case 1:
_talkStealth = 0;
break;
case 2:
_talkStealth = 2;
break;
default:
break;
}
sound.stopSpeech();
events.setCursor(_talkToAbort ? ARROW : oldCursor);
events._pressed = events._released = false;
return key2;
}
bool Talk::isOpcode(byte checkCharacter) {
if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99)))
return false; // outside of range
if (_opcodeTable[checkCharacter - _opcodes[0]])
return true;
return false;
}
void Talk::popStack() {
if (!_scriptStack.empty()) {
ScriptStackEntry scriptEntry = _scriptStack.pop();
_scriptName = scriptEntry._name;
_scriptSaveIndex = scriptEntry._currentIndex;
_scriptSelect = scriptEntry._select;
_scriptMoreFlag = 1;
}
}
void Talk::synchronize(Serializer &s) {
for (uint idx = 0; idx < _talkHistory.size(); ++idx) {
TalkHistoryEntry &he = _talkHistory[idx];
for (int flag = 0; flag < 16; ++flag)
s.syncAsByte(he._data[flag]);
}
}
OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) {
Inventory &inv = *_vm->_inventory;
Common::String tempString;
++str;
for (int idx = 0; idx < str[0]; ++idx)
tempString += str[idx + 1];
str += str[0];
inv.putNameInInventory(tempString);
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) {
Scene &scene = *_vm->_scene;
Common::String tempString;
// Get the name of the object to adjust
++str;
for (int idx = 0; idx < (str[0] & 127); ++idx)
tempString += str[idx + 2];
// Scan for object
int objId = -1;
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name))
objId = idx;
}
if (objId == -1)
error("Could not find object %s to change", tempString.c_str());
// Should the script be overwritten?
if (str[0] > 0x80) {
// Save the current sequence
_savedSequences.push(SequenceEntry());
SequenceEntry &seqEntry = _savedSequences.top();
seqEntry._objNum = objId;
seqEntry._seqTo = scene._bgShapes[objId]._seqTo;
for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx)
seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]);
}
// Get number of bytes to change
_seqCount = str[1];
str += (str[0] & 127) + 2;
// WORKAROUND: Original German Scalpel crash when moving box at Tobacconists
if (_vm->getLanguage() == Common::DE_DEU && _scriptName == "Alfr30Z")
_seqCount = 16;
// Copy in the new sequence
for (int idx = 0; idx < _seqCount; ++idx, ++str)
scene._bgShapes[objId]._sequences[idx] = str[0] - 1;
// Reset object back to beginning of new sequence
scene._bgShapes[objId]._frameNumber = 0;
return RET_CONTINUE;
}
OpcodeReturn Talk::cmdBanishWindow(const byte *&str) {
People &people = *_vm->_people;
UserInterface &ui = *_vm->_ui;
if (!(_speaker & SPEAKER_REMOVE))
people.clearTalking();
pullSequence();
if (_talkToAbort)
return RET_EXIT;
_speaker |= SPEAKER_REMOVE;
ui.banishWindow();
ui._menuMode = TALK_MODE;
_noTextYet = true;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) {
_vm->_ui->_endKeyActive = false;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) {
_vm->_ui->_endKeyActive = true;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) {
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdHolmesOff(const byte *&str) {
People &people = *_vm->_people;
people[HOLMES]._type = REMOVE;
people._holmesOn = false;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdHolmesOn(const byte *&str) {
People &people = *_vm->_people;
people[HOLMES]._type = CHARACTER;
people._holmesOn = true;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdPause(const byte *&str) {
_charCount = *++str;
_wait = _pauseFlag = true;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) {
Events &events = *_vm->_events;
Scene &scene = *_vm->_scene;
++str;
events.incWaitCounter();
for (int idx = 0; idx < (str[0] - 1); ++idx) {
scene.doBgAnim();
if (_talkToAbort)
return RET_EXIT;
// Check for button press
events.pollEvents();
events.setButtonState();
}
events.decWaitCounter();
_endStr = false;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdRemoveItemFromInventory(const byte *&str) {
Inventory &inv = *_vm->_inventory;
Common::String tempString;
++str;
for (int idx = 0; idx < str[0]; ++idx)
tempString += str[idx + 1];
str += str[0];
inv.deleteItemFromInventory(tempString);
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) {
Scene &scene = *_vm->_scene;
++str;
scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1);
if (_talkToAbort)
return RET_EXIT;
// Check if next character is changing side or changing portrait
_wait = 0;
if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] ||
(IS_SERRATED_SCALPEL && str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])))
_wait = 1;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdSetFlag(const byte *&str) {
++str;
int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0);
int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1);
_vm->setFlags(flag);
++str;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdSetObject(const byte *&str) {
Scene &scene = *_vm->_scene;
Common::String tempString;
++str;
for (int idx = 0; idx < (str[0] & 127); ++idx)
tempString += str[idx + 1];
// Set comparison state according to if we want to hide or unhide
bool state = (str[0] >= SPEAKER_REMOVE);
str += str[0] & 127;
for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
Object &object = scene._bgShapes[idx];
if (tempString.equalsIgnoreCase(object._name)) {
// Only toggle the object if it's not in the desired state already
if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state))
object.toggleHidden();
}
}
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdStealthModeActivate(const byte *&str) {
_talkStealth = 2;
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) {
Events &events = *_vm->_events;
_talkStealth = 0;
events.clearKeyboard();
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdToggleObject(const byte *&str) {
Scene &scene = *_vm->_scene;
Common::String tempString;
++str;
for (int idx = 0; idx < str[0]; ++idx)
tempString += str[idx + 1];
scene.toggleObject(tempString);
str += str[0];
return RET_SUCCESS;
}
OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) {
People &people = *_vm->_people;
Scene &scene = *_vm->_scene;
++str;
CAnim &animation = scene._cAnim[str[0] - 1];
people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing);
return _talkToAbort ? RET_EXIT : RET_SUCCESS;
}
void Talk::talkWait(const byte *&str) {
if (!_pauseFlag && _charCount < 160)
_charCount = 160;
_wait = waitForMore(_charCount);
if (_wait == -1)
_endStr = true;
// If a key was pressed to finish the window, see if further voice files should be skipped
if (IS_SERRATED_SCALPEL && _wait >= 0 && _wait < 254) {
if (str[0] == _opcodes[OP_SFX_COMMAND])
str += 9;
}
_pauseFlag = false;
}
} // End of namespace Sherlock