diff options
Diffstat (limited to 'engines/sherlock')
-rw-r--r-- | engines/sherlock/sherlock.cpp | 2 | ||||
-rw-r--r-- | engines/sherlock/talk.cpp | 1029 | ||||
-rw-r--r-- | engines/sherlock/talk.h | 101 |
3 files changed, 712 insertions, 420 deletions
diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index 5973823f96..14f24333e0 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -95,7 +95,7 @@ void SherlockEngine::initialize() { _scene = new Scene(this); _screen = new Screen(this); _sound = new Sound(this, _mixer); - _talk = new Talk(this); + _talk = Talk::init(this); _ui = UserInterface::init(this); // Load game settings diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index bc473f755f..9b22ee1c82 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -231,6 +231,13 @@ void TalkSequences::clear() { /*----------------------------------------------------------------*/ +Talk *Talk::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new ScalpelTalk(vm); + else + return new TattooTalk(vm); +} + Talk::Talk(SherlockEngine *vm) : _vm(vm) { _talkCounter = 0; _talkToAbort = false; @@ -245,6 +252,14 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) { _scriptMoreFlag = 0; _scriptSaveIndex = -1; _opcodes = IS_SERRATED_SCALPEL ? SCALPEL_OPCODES : TATTOO_OPCODES; + + _charCount = 0; + _line = 0; + _yp = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _scriptStart = _scriptEnd = nullptr; } void Talk::talkTo(const Common::String &filename) { @@ -1063,34 +1078,29 @@ void Talk::setStillSeq(int speaker) { } void Talk::doScript(const Common::String &script) { - Animation &anim = *_vm->_animation; - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Map &map = *_vm->_map; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; - int wait = 0; - bool pauseFlag = false; - bool endStr = false; - int yp = CONTROLS_Y + 12; - int charCount = 0; - int line = 0; - bool noTextYet = true; bool openTalkWindow = false; - int seqCount; _savedSequences.clear(); - const byte *scriptStart = (const byte *)script.c_str(); - const byte *scriptEnd = scriptStart + script.size(); - const byte *str = scriptStart; + _scriptStart = (const byte *)script.c_str(); + _scriptEnd = _scriptStart + script.size(); + const byte *str = _scriptStart; + _yp = CONTROLS_Y + 12; + _charCount = 0; + _line = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _noTextYet = true; + _endStr = false; if (_scriptMoreFlag) { _scriptMoreFlag = 0; - str = scriptStart + _scriptSaveIndex; + str = _scriptStart + _scriptSaveIndex; } // Check if the script begins with a Stealh Mode Active command @@ -1145,371 +1155,31 @@ void Talk::doScript(const Common::String &script) { do { Common::String tempString; - wait = 0; + _wait = 0; byte c = str[0]; if (!c) { - endStr = true; + _endStr = true; } else if (c == '{') { // Start of comment, so skip over it while (*str++ != '}') ; - } else if (c >= _opcodes[0]) { + } else if (_opcodeTable[c]) { // Handle control code - if (c == _opcodes[OP_SWITCH_SPEAKER]) { - if (!(_speaker & SPEAKER_REMOVE)) - people.clearTalking(); - if (_talkToAbort) - return; - - ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; - - _speaker = *++str - 1; - people.setTalking(_speaker); - pullSequence(); - pushSequence(_speaker); - setSequence(_speaker); - - } else if (c == _opcodes[OP_RUN_CANIMATION]) { - ++str; - scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1); - if (_talkToAbort) - return; - - // Check if next character is changing side or changing portrait - if (charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])) - wait = 1; - - } else if (c == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { - ++str; - switch (str[0] & 15) { - case 1: - people._portraitSide = 20; - break; - case 2: - people._portraitSide = 220; - break; - case 3: - people._portraitSide = 120; - break; - default: - break; - } - - if (str[0] > 15) - people._speakerFlip = true; - - } else if (c == _opcodes[OP_PAUSE]) { - // Pause - charCount = *++str; - wait = pauseFlag = true; - - } else if (c == _opcodes[OP_REMOVE_PORTRAIT]) { - if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) - people.clearTalking(); - pullSequence(); - if (_talkToAbort) - return; - - _speaker |= SPEAKER_REMOVE; - - } else if (c == _opcodes[OP_CLEAR_WINDOW]) { - ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; - - } else if (c == _opcodes[OP_ADJUST_OBJ_SEQUENCE]) { - // 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; - - // 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; + switch ((this->*_opcodeTable[c - 128])(str)) { + case RET_EXIT: + return; + case RET_CONTINUE: continue; - - } else if (c == _opcodes[OP_WALK_TO_COORDS]) { - ++str; - - people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100, - str[2] * 100), str[3] - 1); - if (_talkToAbort) - return; - - str += 3; - - } else if (c == _opcodes[OP_PAUSE_WITHOUT_CONTROL]) { - ++str; - - for (int idx = 0; idx < (str[0] - 1); ++idx) { - scene.doBgAnim(); - if (_talkToAbort) - return; - - // Check for button press - events.pollEvents(); - events.setButtonState(); - } - - } else if (c == _opcodes[OP_BANISH_WINDOW]) { - if (!(_speaker & SPEAKER_REMOVE)) - people.clearTalking(); - pullSequence(); - - if (_talkToAbort) - return; - - _speaker |= SPEAKER_REMOVE; - ui.banishWindow(); - ui._menuMode = TALK_MODE; - noTextYet = true; - - } else if (c == _opcodes[OP_SUMMON_WINDOW]) { - drawInterface(); - events._pressed = events._released = false; - events.clearKeyboard(); - noTextYet = false; - - if (_speaker != -1) { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down"); - } - - } else if (c == _opcodes[OP_SET_FLAG]) { - ++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; - - } else if (c == _opcodes[OP_SFX_COMMAND]) { - ++str; - if (sound._voices) { - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - sound.playSound(tempString, WAIT_RETURN_IMMEDIATELY); - - // Set voices to wait for more - sound._voices = 2; - sound._speechOn = (*sound._soundIsOn); - } - - wait = 1; - str += 7; - - } else if (c == _opcodes[OP_TOGGLE_OBJECT]) { - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - - scene.toggleObject(tempString); - str += str[0]; - - } else if (c == _opcodes[OP_STEALTH_MODE_ACTIVE]) { - _talkStealth = 2; - - } else if (c == _opcodes[OP_IF_STATEMENT]) { - ++str; - int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); - ++str; - wait = 0; - - bool result = flag < 0x8000; - if (_vm->readFlags(flag & 0x7fff) != result) { - do { - ++str; - } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]); - - if (!str[0]) - endStr = true; - } - - } else if (c == _opcodes[OP_ELSE_STATEMENT]) { - // If this is encountered here, it means that a preceeding IF statement was found, - // and evaluated to true. Now all the statements for the true block are finished, - // so skip over the block of code that would have executed if the result was false - wait = 0; - do { - ++str; - } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]); - - } else if (c == _opcodes[OP_STEALTH_MODE_DEACTIVATE]) { - _talkStealth = 0; - events.clearKeyboard(); - - } else if (c == _opcodes[OP_TURN_HOLMES_OFF]) { - people._holmesOn = false; - - } else if (c == _opcodes[OP_TURN_HOLMES_ON]) { - people._holmesOn = true; - - } else if (c == _opcodes[OP_GOTO_SCENE]) { - scene._goToScene = str[1] - 1; - - if (scene._goToScene != 100) { - // Not going to the map overview - map._oldCharPoint = scene._goToScene; - map._overPos.x = map[scene._goToScene].x * 100 - 600; - map._overPos.y = map[scene._goToScene].y * 100 + 900; - - // Run a canimation? - if (str[2] > 100) { - people._hSavedFacing = str[2]; - people._hSavedPos = Common::Point(160, 100); - } - } - str += 6; - - _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; - _scriptSaveIndex = str - scriptStart; - endStr = true; - wait = 0; - - } else if (c == _opcodes[OP_PLAY_PROLOGUE]) { - ++str; - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - - anim.play(tempString, 1, 3, true, 4); - - } else if (c == _opcodes[OP_ADD_ITEM_TO_INVENTORY]) { - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - inv.putNameInInventory(tempString); - - } else if (c == _opcodes[OP_SET_OBJECT]) { - ++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(); - } - } - - } else if (c == _opcodes[OP_CALL_TALK_FILE]) { - ++str; - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - str += 8; - - int scriptCurrentIndex = str - scriptStart; - - // Save the current script position and new talk file - if (_scriptStack.size() < 9) { - ScriptStackEntry rec1; - rec1._name = _scriptName; - rec1._currentIndex = scriptCurrentIndex; - rec1._select = _scriptSelect; - _scriptStack.push(rec1); - - // Push the new talk file onto the stack - ScriptStackEntry rec2; - rec2._name = tempString; - rec2._currentIndex = 0; - rec2._select = 100; - _scriptStack.push(rec2); - } else { - error("Script stack overflow"); - } - - _scriptMoreFlag = 1; - endStr = true; - wait = 0; - - } else if (c == _opcodes[OP_MOVE_MOUSE]) { - ++str; - events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); - if (_talkToAbort) - return; - str += 3; - - } else if (c == _opcodes[OP_DISPLAY_INFO_LINE]) { - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str()); - ui._menuCounter = 30; - - } else if (c == _opcodes[OP_CLEAR_INFO_LINE]) { - ui._infoFlag = true; - ui.clearInfo(); - - } else if (c == _opcodes[OP_WALK_TO_CANIMATION]) { - ++str; - CAnim &animation = scene._cAnim[str[0] - 1]; - - people.walkToCoords(animation._goto, animation._gotoDir); - if (_talkToAbort) - return; - - } else if (c == _opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) { - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - inv.deleteItemFromInventory(tempString); - - } else if (c == _opcodes[OP_ENABLE_END_KEY]) { - ui._endKeyActive = true; - - } else if (c == _opcodes[OP_DISABLE_END_KEY]) { - ui._endKeyActive = false; - - } else { - error("Unknown opcode encountered"); + default: + break; } ++str; } else { // If the window isn't yet open, draw the window before printing starts - if (!ui._windowOpen && noTextYet) { - noTextYet = false; + if (!ui._windowOpen && _noTextYet) { + _noTextYet = false; drawInterface(); if (_talkTo != -1) { @@ -1520,19 +1190,19 @@ void Talk::doScript(const Common::String &script) { } // If it's the first line, display the speaker - if (!line && _speaker >= 0 && _speaker < (int)people._characters.size()) { + if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) { // If the window is open, display the name directly on-screen. // Otherwise, simply draw it on the back buffer if (ui._windowOpen) { - screen.print(Common::Point(16, yp), TALK_FOREGROUND, "%s", + screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s", people._characters[_speaker & 127]._name); } else { - screen.gPrint(Common::Point(16, yp - 1), TALK_FOREGROUND, "%s", + screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s", people._characters[_speaker & 127]._name); openTalkWindow = true; } - yp += 9; + _yp += 9; } // Find amount of text that will fit on the line @@ -1540,23 +1210,23 @@ void Talk::doScript(const Common::String &script) { do { width += screen.charWidth(str[idx]); ++idx; - ++charCount; + ++_charCount; } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < _opcodes[0]); if (str[idx] || width >= 298) { if (str[idx] < _opcodes[0] && str[idx] != '{') { --idx; - --charCount; + --_charCount; } } else { - endStr = true; + _endStr = true; } // If word wrap is needed, find the start of the current word if (width >= 298) { while (str[idx] != ' ') { --idx; - --charCount; + --_charCount; } } @@ -1566,16 +1236,16 @@ void Talk::doScript(const Common::String &script) { // If the speaker indicates a description file, print it in yellow if (_speaker != -1) { if (ui._windowOpen) { - screen.print(Common::Point(16, yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); } else { - screen.gPrint(Common::Point(16, yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); openTalkWindow = true; } } else { if (ui._windowOpen) { - screen.print(Common::Point(16, yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); } else { - screen.gPrint(Common::Point(16, yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); openTalkWindow = true; } } @@ -1587,24 +1257,24 @@ void Talk::doScript(const Common::String &script) { if (str[0] < _opcodes[0] && str[0] != '{') ++str; - yp += 9; - ++line; + _yp += 9; + ++_line; // Certain different conditions require a wait - if ((line == 4 && str < scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) || - (line == 5 && str < scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) || - endStr) { - wait = 1; + if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) || + (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) || + _endStr) { + _wait = 1; } - byte v = (str >= scriptEnd ? 0 : str[0]); - wait = v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + byte v = (str >= _scriptEnd ? 0 : str[0]); + _wait = v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || v == _opcodes[OP_BANISH_WINDOW] || _opcodes[OP_IF_STATEMENT] || v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] || v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]; } // 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_CARRIAGE_RETURN])) { + if ((openTalkWindow && _wait) || (openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_CARRIAGE_RETURN])) { if (!ui._slideWindows) { screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); } else { @@ -1615,34 +1285,34 @@ void Talk::doScript(const Common::String &script) { openTalkWindow = false; } - if (wait) { + if (_wait) { // Handling pausing - if (!pauseFlag && charCount < 160) - charCount = 160; + if (!_pauseFlag && _charCount < 160) + _charCount = 160; - wait = waitForMore(charCount); - if (wait == -1) - endStr = true; + _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 (wait >= 0 && wait < 254) { + if (_wait >= 0 && _wait < 254) { if (str[0] == _opcodes[OP_SFX_COMMAND]) str += 9; } // Clear the window unless the wait was due to a PAUSE command - if (!pauseFlag && wait != -1 && str < scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { + if (!_pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { if (!_talkStealth) ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; } - pauseFlag = false; + _pauseFlag = false; } - } while (!_vm->shouldQuit() && !endStr); + } while (!_vm->shouldQuit() && !_endStr); - if (wait != -1) { + if (_wait != -1) { for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) { SequenceEntry &seq = _savedSequences[ssIndex]; Object &object = scene._bgShapes[seq._objNum]; @@ -1757,4 +1427,559 @@ void Talk::synchronize(Common::Serializer &s) { } } +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; + + // 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::cmdGotoScene(const byte *&str) { + Map &map = *_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + scene._goToScene = str[1] - 1; + + if (scene._goToScene != 100) { + // Not going to the map overview + map._oldCharPoint = scene._goToScene; + map._overPos.x = map[scene._goToScene].x * 100 - 600; + map._overPos.y = map[scene._goToScene].y * 100 + 900; + + // Run a canimation? + if (str[2] > 100) { + people._hSavedFacing = str[2]; + people._hSavedPos = Common::Point(160, 100); + } + } + str += 6; + + _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; + _scriptSaveIndex = str - _scriptStart; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOff(const byte *&str) { + _vm->_people->_holmesOn = false; + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOn(const byte *&str) { + _vm->_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; + + for (int idx = 0; idx < (str[0] - 1); ++idx) { + scene.doBgAnim(); + if (_talkToAbort) + return RET_EXIT; + + // Check for button press + events.pollEvents(); + events.setButtonState(); + } + + 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 + if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || 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::cmdSwitchSpeaker(const byte *&str) { + People &people = *_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!(_speaker & SPEAKER_REMOVE)) + people.clearTalking(); + if (_talkToAbort) + return RET_EXIT; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + _speaker = *++str - 1; + people.setTalking(_speaker); + pullSequence(); + pushSequence(_speaker); + setSequence(_speaker); + + 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.walkToCoords(animation._goto, animation._gotoDir); + + return _talkToAbort ? RET_EXIT : RET_SUCCESS; +} + +OpcodeReturn Talk::cmdWalkToCoords(const byte *&str) { + People &people = *_vm->_people; + ++str; + + people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100, + str[2] * 100), str[3] - 1); + if (_talkToAbort) + return RET_EXIT; + + str += 3; + return RET_SUCCESS; +} + +/*----------------------------------------------------------------*/ + +ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker, + (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation, + + (OpcodeMethod)&ScalpelTalk::cmdPause, + (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait, + (OpcodeMethod)&ScalpelTalk::cmdClearWindow, + (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords, + (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl, + (OpcodeMethod)&ScalpelTalk::cmdBanishWindow, + (OpcodeMethod)&ScalpelTalk::cmdSummonWindow, + (OpcodeMethod)&ScalpelTalk::cmdSetFlag, + (OpcodeMethod)&ScalpelTalk::cmdSfxCommand, + + (OpcodeMethod)&ScalpelTalk::cmdToggleObject, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate, + (OpcodeMethod)&ScalpelTalk::cmdIf, + (OpcodeMethod)&ScalpelTalk::cmdElse, + nullptr, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOff, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOn, + (OpcodeMethod)&ScalpelTalk::cmdGotoScene, + (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue, + + (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory, + (OpcodeMethod)&ScalpelTalk::cmdSetObject, + (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile, + (OpcodeMethod)&ScalpelTalk::cmdMoveMouse, + (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory, + (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey, + (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey, + + (OpcodeMethod)&ScalpelTalk::cmdCarriageReturn + }; + + _opcodeTable = OPCODE_METHODS; +} + +OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) { + People &people = *_vm->_people; + + ++str; + switch (str[0] & 15) { + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + default: + break; + } + + if (str[0] > 15) + people._speakerFlip = true; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdCallTalkFile(const byte *&str) { + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + str += 8; + + int scriptCurrentIndex = str - _scriptStart; + + // Save the current script position and new talk file + if (_scriptStack.size() < 9) { + ScriptStackEntry rec1; + rec1._name = _scriptName; + rec1._currentIndex = scriptCurrentIndex; + rec1._select = _scriptSelect; + _scriptStack.push(rec1); + + // Push the new talk file onto the stack + ScriptStackEntry rec2; + rec2._name = tempString; + rec2._currentIndex = 0; + rec2._select = 100; + _scriptStack.push(rec2); + } else { + error("Script stack overflow"); + } + + _scriptMoreFlag = 1; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui._infoFlag = true; + ui.clearInfo(); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) { + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str()); + ui._menuCounter = 30; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) { + // If this is encountered here, it means that a preceeding IF statement was found, + // and evaluated to true. Now all the statements for the true block are finished, + // so skip over the block of code that would have executed if the result was false + _wait = 0; + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) { + ++str; + int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + ++str; + _wait = 0; + + bool result = flag < 0x8000; + if (_vm->readFlags(flag & 0x7fff) != result) { + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + if (!str[0]) + _endStr = true; + } + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) { + Events &events = *_vm->_events; + + ++str; + events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); + if (_talkToAbort) + return RET_EXIT; + str += 3; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) { + Animation &anim = *_vm->_animation; + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + + anim.play(tempString, 1, 3, true, 4); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) { + People &people = *_vm->_people; + + if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) + people.clearTalking(); + pullSequence(); + if (_talkToAbort) + return RET_EXIT; + + _speaker |= SPEAKER_REMOVE; + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) { + Sound &sound = *_vm->_sound; + Common::String tempString; + + ++str; + if (sound._voices) { + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + sound.playSound(tempString, WAIT_RETURN_IMMEDIATELY); + + // Set voices to wait for more + sound._voices = 2; + sound._speechOn = (*sound._soundIsOn); + } + + _wait = 1; + str += 7; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + drawInterface(); + events._pressed = events._released = false; + events.clearKeyboard(); + _noTextYet = false; + + if (_speaker != -1) { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down"); + } + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdCarriageReturn(const byte *&str) { + return RET_SUCCESS; +} + +/*----------------------------------------------------------------*/ + +TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker, + (OpcodeMethod)&TattooTalk::cmdRunCAnimation, + // TODO: Implement opcode methods for new Tattoo opcodes + }; + + _opcodeTable = OPCODE_METHODS; +} + } // End of namespace Sherlock diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h index b30b4ad714..02d141db75 100644 --- a/engines/sherlock/talk.h +++ b/engines/sherlock/talk.h @@ -105,6 +105,13 @@ enum { OP_TURN_SOUNDS_OFF = 64 }; +enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 }; + +class SherlockEngine; +class ScalpelUserInterface; +class Talk; + +typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str); struct SequenceEntry { int _objNum; @@ -156,25 +163,9 @@ struct TalkSequences { void clear(); }; -class SherlockEngine; -class ScalpelUserInterface; - class Talk { friend class ScalpelUserInterface; private: - SherlockEngine *_vm; - Common::Stack<SequenceEntry> _savedSequences; - Common::Stack<SequenceEntry> _sequenceStack; - Common::Stack<ScriptStackEntry> _scriptStack; - Common::Array<Statement> _statements; - TalkHistoryEntry _talkHistory[MAX_TALK_FILES]; - int _speaker; - int _talkIndex; - int _scriptSelect; - int _talkStealth; - int _talkToFlag; - int _scriptSaveIndex; -private: /** * Remove any voice commands from a loaded statement list */ @@ -207,6 +198,54 @@ private: * the amount of text that's been displayed */ int waitForMore(int delay); +protected: + SherlockEngine *_vm; + OpcodeMethod *_opcodeTable; + Common::Stack<SequenceEntry> _savedSequences; + Common::Stack<SequenceEntry> _sequenceStack; + Common::Stack<ScriptStackEntry> _scriptStack; + Common::Array<Statement> _statements; + TalkHistoryEntry _talkHistory[MAX_TALK_FILES]; + int _speaker; + int _talkIndex; + int _scriptSelect; + int _talkStealth; + int _talkToFlag; + int _scriptSaveIndex; + + // These fields are used solely by doScript, but are fields because all the script opcodes are + // separate methods now, and need access to these fields + int _yp; + int _charCount; + int _line; + int _wait; + bool _pauseFlag; + bool _endStr, _noTextYet; + int _seqCount; + const byte *_scriptStart, *_scriptEnd; +protected: + Talk(SherlockEngine *vm); + + OpcodeReturn cmdAddItemToInventory(const byte *&str); + OpcodeReturn cmdAdjustObjectSequence(const byte *&str); + OpcodeReturn cmdBanishWindow(const byte *&str); + OpcodeReturn cmdDisableEndKey(const byte *&str); + OpcodeReturn cmdEnableEndKey(const byte *&str); + OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdHolmesOff(const byte *&str); + OpcodeReturn cmdHolmesOn(const byte *&str); + OpcodeReturn cmdPause(const byte *&str); + OpcodeReturn cmdPauseWithoutControl(const byte *&str); + OpcodeReturn cmdRemoveItemFromInventory(const byte *&str); + OpcodeReturn cmdRunCAnimation(const byte *&str); + OpcodeReturn cmdSetFlag(const byte *&str); + OpcodeReturn cmdSetObject(const byte *&str); + OpcodeReturn cmdStealthModeActivate(const byte *&str); + OpcodeReturn cmdStealthModeDeactivate(const byte *&str); + OpcodeReturn cmdSwitchSpeaker(const byte *&str); + OpcodeReturn cmdToggleObject(const byte *&str); + OpcodeReturn cmdWalkToCAnimation(const byte *&str); + OpcodeReturn cmdWalkToCoords(const byte *&str); public: bool _talkToAbort; int _talkCounter; @@ -216,8 +255,10 @@ public: bool _moreTalkUp, _moreTalkDown; int _converseNum; const byte *_opcodes; + public: - Talk(SherlockEngine *vm); + static Talk *init(SherlockEngine *vm); + virtual ~Talk() {} /** * Return a given talk statement @@ -302,6 +343,32 @@ public: void synchronize(Common::Serializer &s); }; +class ScalpelTalk : public Talk { +protected: + OpcodeReturn cmdAssignPortraitLocation(const byte *&str); + OpcodeReturn cmdCallTalkFile(const byte *&str); + OpcodeReturn cmdClearInfoLine(const byte *&str); + OpcodeReturn cmdClearWindow(const byte *&str); + OpcodeReturn cmdDisplayInfoLine(const byte *&str); + OpcodeReturn cmdElse(const byte *&str); + OpcodeReturn cmdIf(const byte *&str); + OpcodeReturn cmdMoveMouse(const byte *&str); + OpcodeReturn cmdPlayPrologue(const byte *&str); + OpcodeReturn cmdRemovePortrait(const byte *&str); + OpcodeReturn cmdSfxCommand(const byte *&str); + OpcodeReturn cmdSummonWindow(const byte *&str); + OpcodeReturn cmdCarriageReturn(const byte *&str); +public: + ScalpelTalk(SherlockEngine *vm); + virtual ~ScalpelTalk() {} +}; + +class TattooTalk : public Talk { +public: + TattooTalk(SherlockEngine *vm); + virtual ~TattooTalk() {} +}; + } // End of namespace Sherlock #endif |