diff options
Diffstat (limited to 'engines/sherlock/talk.cpp')
-rw-r--r-- | engines/sherlock/talk.cpp | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp new file mode 100644 index 0000000000..3c6bf44837 --- /dev/null +++ b/engines/sherlock/talk.cpp @@ -0,0 +1,1219 @@ +/* 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 |