From 96e04ab797253bbe853f172ea1d734ebe812d419 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Fri, 10 Apr 2015 23:35:26 -0500 Subject: SHERLOCK: Implemented doScript and support methods --- engines/sherlock/animation.cpp | 2 +- engines/sherlock/events.cpp | 8 + engines/sherlock/events.h | 2 + engines/sherlock/inventory.cpp | 112 +++++ engines/sherlock/inventory.h | 9 + engines/sherlock/people.cpp | 2 + engines/sherlock/people.h | 2 + engines/sherlock/scene.cpp | 16 +- engines/sherlock/scripts.cpp | 11 +- engines/sherlock/scripts.h | 6 +- engines/sherlock/sherlock.cpp | 6 +- engines/sherlock/sound.cpp | 7 +- engines/sherlock/sound.h | 4 +- engines/sherlock/talk.cpp | 833 +++++++++++++++++++++++++++++++++++- engines/sherlock/talk.h | 31 +- engines/sherlock/user_interface.cpp | 10 +- 16 files changed, 1002 insertions(+), 59 deletions(-) diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp index e1687b5238..4674151ec7 100644 --- a/engines/sherlock/animation.cpp +++ b/engines/sherlock/animation.cpp @@ -144,7 +144,7 @@ bool Animation::playPrologue(const Common::String &filename, int minDelay, int f Common::String::format("%s%01d", baseName.c_str(), soundNumber) : Common::String::format("%s%02d", baseName.c_str(), soundNumber); - if (sound._voicesOn) + if (sound._voices) sound.playSound(fname); } diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp index d147fe1f4c..67d09e52b2 100644 --- a/engines/sherlock/events.cpp +++ b/engines/sherlock/events.cpp @@ -100,6 +100,14 @@ bool Events::isCursorVisible() const { return CursorMan.isVisible(); } +/** + * Move the mouse + */ +void Events::moveMouse(const Common::Point &pt) { + g_system->warpMouse(pt.x, pt.y); +} + + /** * Check for any pending events */ diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h index c3bdaf5a93..71f7623002 100644 --- a/engines/sherlock/events.h +++ b/engines/sherlock/events.h @@ -72,6 +72,8 @@ public: bool isCursorVisible() const; + void moveMouse(const Common::Point &pt); + void pollEvents(); void pollEventsAndWait(); diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp index e58c4ddac6..0ea85f32fc 100644 --- a/engines/sherlock/inventory.cpp +++ b/engines/sherlock/inventory.cpp @@ -366,4 +366,116 @@ void Inventory::doInvJF() { } } +/** + * Adds a shape from the scene to the player's inventory + */ +int Inventory::putNameInInventory(const Common::String &name) { + Scene &scene = *_vm->_scene; + int matches = 0; + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &o = scene._bgShapes[idx]; + if (scumm_stricmp(name.c_str(), o._name.c_str()) == 0 && o._type != INVALID) { + putItemInInventory(o); + ++matches; + } + } + + return matches; +} + +/** + * Moves a specified item into the player's inventory If the item has a *PICKUP* use action, + * then the item in the use action are added to the inventory. + */ +int Inventory::putItemInInventory(Object &obj) { + Scene &scene = *_vm->_scene; + int matches = 0; + bool pickupFound = false; + + if (obj._pickupFlag) + _vm->setFlags(obj._pickupFlag); + + for (int useNum = 0; useNum < 4; ++useNum) { + if (scumm_stricmp(obj._use[useNum]._target.c_str(), "*PICKUP*") == 0) { + pickupFound = true; + + for (int namesNum = 0; namesNum < 4; ++namesNum) { + for (uint bgNum = 0; bgNum < scene._bgShapes.size(); ++bgNum) { + Object &bgObj = scene._bgShapes[bgNum]; + if (scumm_stricmp(obj._use[useNum]._names[namesNum].c_str(), bgObj._name.c_str()) == 0) { + copyToInventory(bgObj); + if (bgObj._pickupFlag) + _vm->setFlags(bgObj._pickupFlag); + + if (bgObj._type == ACTIVE_BG_SHAPE || bgObj._type == NO_SHAPE || bgObj._type == HIDE_SHAPE) { + if (bgObj._imageFrame == nullptr || bgObj._frameNumber < 0) + // No shape to erase, so flag as hidden + bgObj._type = INVALID; + else + bgObj._type = REMOVE; + } else if (bgObj._type == HIDDEN) { + bgObj._type = INVALID; + } + + ++matches; + } + } + } + } + } + + if (!pickupFound) { + // No pickup item found, so add the passed item + copyToInventory(obj); + matches = 0; + } + + if (matches == 0) { + if (!pickupFound) + matches = 1; + + if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE || obj._type == HIDE_SHAPE) { + if (obj._imageFrame == nullptr || obj._frameNumber < 0) + // No shape to erase, so flag as hidden + obj._type = INVALID; + else + obj._type = REMOVE; + } else if (obj._type == HIDDEN) { + obj._type = INVALID; + } + } + + return matches; +} + +/** + * Copy the passed object into the inventory + */ +void Inventory::copyToInventory(Object &obj) { + // TODO +} + +/** + * Deletes a specified item from the player's inventory + */ +int Inventory::deleteItemFromInventory(const Common::String &name) { + int invNum = -1; + + for (int idx = 0; idx < (int)size() && invNum == -1; ++idx) { + if (scumm_stricmp(name.c_str(), (*this)[idx]._name.c_str()) == 0) + invNum = idx; + } + + if (invNum == -1) + // Item not present + return 0; + + // Item found, so delete it + remove_at(invNum); + --_holdings; + + return 1; +} + } // End of namespace Sherlock diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h index 55abc4c960..722692a3b2 100644 --- a/engines/sherlock/inventory.h +++ b/engines/sherlock/inventory.h @@ -26,6 +26,7 @@ #include "common/scummsys.h" #include "common/array.h" #include "common/str-array.h" +#include "sherlock/objects.h" #include "sherlock/resources.h" namespace Sherlock { @@ -59,6 +60,10 @@ struct InventoryItem { class Inventory : public Common::Array { private: SherlockEngine *_vm; + + int putItemInInventory(Object &obj); + + void copyToInventory(Object &obj); public: ImageFile *_invShapes[MAX_VISIBLE_INVENTORY]; Common::StringArray _names; @@ -90,6 +95,10 @@ public: void highlight(int index, byte color); void doInvJF(); + + int putNameInInventory(const Common::String &name); + + int deleteItemFromInventory(const Common::String &name); }; } // End of namespace Sherlock diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp index 3da35f2fec..9319fbb79d 100644 --- a/engines/sherlock/people.cpp +++ b/engines/sherlock/people.cpp @@ -61,6 +61,8 @@ People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) { _clearingThePortrait = false; _srcZone = _destZone = 0; _talkPics = nullptr; + _portraitSide = 0; + _speakerFlip = false; } People::~People() { diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h index a1fad019c8..1a846dded1 100644 --- a/engines/sherlock/people.h +++ b/engines/sherlock/people.h @@ -77,6 +77,8 @@ public: Object _portrait; bool _clearingThePortrait; bool _allowWalkAbort; + int _portraitSide; + bool _speakerFlip; public: People(SherlockEngine *vm); ~People(); diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 7c66a1dc62..3d5f566164 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -89,7 +89,8 @@ Scene::Scene(SherlockEngine *vm): _vm(vm) { _currentScene = -1; _goToScene = -1; _changes = false; - _charPoint = _oldCharPoint = 0; + _charPoint = 0; + _oldCharPoint = 39; _keyboardInput = 0; _walkedInScene = false; _ongoingCans = 0; @@ -118,7 +119,7 @@ void Scene::selectScene() { Events &events = *_vm->_events; People &people = *_vm->_people; Screen &screen = *_vm->_screen; - Scripts &scripts = *_vm->_scripts; + Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; // Reset fields @@ -150,8 +151,8 @@ void Scene::selectScene() { // If there were any scripst waiting to be run, but were interrupt by a running // canimation (probably the last scene's exit canim), clear the _scriptMoreFlag - if (scripts._scriptMoreFlag == 3) - scripts._scriptMoreFlag = 0; + if (talk._scriptMoreFlag == 3) + talk._scriptMoreFlag = 0; } /** @@ -1053,7 +1054,6 @@ void Scene::doBgAnim() { Inventory &inv = *_vm->_inventory; People &people = *_vm->_people; Screen &screen = *_vm->_screen; - Scripts &scripts = *_vm->_scripts; Sound &sound = *_vm->_sound; Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; @@ -1359,10 +1359,10 @@ void Scene::doBgAnim() { // Check if the method was called for calling a portrait, and a talk was // interrupting it. This talk file would not have been executed at the time, // since we needed to finish the 'doBgAnim' to finish clearing the portrait - if (people._clearingThePortrait && scripts._scriptMoreFlag == 3) { + if (people._clearingThePortrait && talk._scriptMoreFlag == 3) { // Reset the flags and call to talk - people._clearingThePortrait = scripts._scriptMoreFlag = 0; - talk.talkTo(scripts._scriptName); + people._clearingThePortrait = talk._scriptMoreFlag = 0; + talk.talkTo(talk._scriptName); } } diff --git a/engines/sherlock/scripts.cpp b/engines/sherlock/scripts.cpp index d337030bae..02dcfa6f0b 100644 --- a/engines/sherlock/scripts.cpp +++ b/engines/sherlock/scripts.cpp @@ -26,21 +26,16 @@ namespace Sherlock { Scripts::Scripts(SherlockEngine *vm): _vm(vm) { - _scriptMoreFlag = 0; - _scriptSaveIndex = 0; - _scriptSelect = 0; -} -void Scripts::doScript(const Common::String &str) { - // TODO } void Scripts::popStack() { + /* ScriptEntry script = _scriptStack.pop(); _scriptName = script._name; - _scriptSaveIndex = script._index; +// _scriptSaveIndex = script._index; _scriptSelect = script._select; - _scriptMoreFlag = true; + */ } diff --git a/engines/sherlock/scripts.h b/engines/sherlock/scripts.h index 6765687bcf..beea726c8d 100644 --- a/engines/sherlock/scripts.h +++ b/engines/sherlock/scripts.h @@ -40,11 +40,7 @@ class Scripts { private: SherlockEngine *_vm; public: - Common::Stack _scriptStack; - int _scriptMoreFlag; - Common::String _scriptName; - int _scriptSaveIndex; - int _scriptSelect; + public: Scripts(SherlockEngine *vm); diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index 20a805594e..2a1b456b76 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -122,10 +122,10 @@ void SherlockEngine::sceneLoop() { while (!shouldQuit() && _scene->_goToScene == -1) { // See if a script needs to be completed from either a goto room code, // or a script that was interrupted by another script - if (_scripts->_scriptMoreFlag == 1 || _scripts->_scriptMoreFlag == 3) - _talk->talkTo(_scripts->_scriptName); + if (_talk->_scriptMoreFlag == 1 || _talk->_scriptMoreFlag == 3) + _talk->talkTo(_talk->_scriptName); else - _scripts->_scriptMoreFlag = 0; + _talk->_scriptMoreFlag = 0; // Handle any input from the keyboard or mouse handleInput(); diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp index 5e7df5607f..771d5db9d5 100644 --- a/engines/sherlock/sound.cpp +++ b/engines/sherlock/sound.cpp @@ -27,7 +27,8 @@ namespace Sherlock { Sound::Sound(SherlockEngine *vm): _vm(vm) { _soundOn = true; _musicOn = true; - _voicesOn = true; + _speechOn = true; + _voices = 0; _playingEpilogue = false; _music = false; _digitized = false; @@ -89,5 +90,9 @@ void Sound::freeSong() { // TODO } +void Sound::stopSndFuncPtr(int v1, int v2) { + // TODO +} + } // End of namespace Sherlock diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h index 74e8db3611..22d5a5c221 100644 --- a/engines/sherlock/sound.h +++ b/engines/sherlock/sound.h @@ -40,7 +40,8 @@ private: public: bool _soundOn; bool _musicOn; - bool _voicesOn; + bool _speechOn; + int _voices; bool _playingEpilogue; bool _music; bool _digitized; @@ -64,6 +65,7 @@ public: void playMusic(const Common::String &name); void stopMusic(); + void stopSndFuncPtr(int v1, int v2); }; } // End of namespace Sherlock diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index d284ace8b8..193c0f9a19 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -26,7 +26,42 @@ namespace Sherlock { -#define SFX_COMMAND 140 +enum { + SWITCH_SPEAKER = 128, + RUN_CANIMATION = 129, + ASSIGN_PORTRAIT_LOCATION = 130, + PAUSE = 131, + REMOVE_PORTRAIT = 132, + CLEAR_WINDOW = 133, + ADJUST_OBJ_SEQUENCE = 134, + WALK_TO_COORDS = 135, + PAUSE_WITHOUT_CONTROL = 136, + BANISH_WINDOW = 137, + SUMMON_WINDOW = 138, + SET_FLAG = 139, + SFX_COMMAND = 140, + TOGGLE_OBJECT = 141, + STEALTH_MODE_ACTIVE = 142, + IF_STATEMENT = 143, + ELSE_STATEMENT = 144, + END_IF_STATEMENT = 145, + STEALTH_MODE_DEACTIVATE = 146, + TURN_HOLMES_OFF = 147, + TURN_HOLMES_ON = 148, + GOTO_SCENE = 149, + PLAY_PROLOGUE = 150, + ADD_ITEM_TO_INVENTORY = 151, + SET_OBJECT = 152, + CALL_TALK_FILE = 153, + MOVE_MOUSE = 154, + DISPLAY_INFO_LINE = 155, + CLEAR_INFO_LINE = 156, + WALK_TO_CANIMATION = 157, + REMOVE_ITEM_FROM_INVENTORY = 158, + ENABLE_END_KEY = 159, + DISABLE_END_KEY = 160, + COMMAND_161 = 161 +}; /*----------------------------------------------------------------*/ @@ -99,6 +134,9 @@ Talk::Talk(SherlockEngine *vm): _vm(vm) { _talkStealth = 0; _talkToFlag = -1; _moreTalkDown = _moreTalkUp = false; + _scriptMoreFlag = 1; + _scriptSaveIndex = -1; + _scriptCurrentIndex = -1; } void Talk::setSequences(const byte *talkSequences, const byte *stillSequences, int maxPeople) { @@ -136,13 +174,13 @@ void Talk::talkTo(const Common::String &filename) { // save the filename for later executing when the canimation is done if (scene._ongoingCans || people._clearingThePortrait) { // Make sure we're not in the middle of a script - if (!scripts._scriptMoreFlag) { - scripts._scriptName = filename; - scripts._scriptSaveIndex = 0; + if (!_scriptMoreFlag) { + _scriptName = filename; + _scriptSaveIndex = 0; // Flag the selection, since we don't yet know which statement yet - scripts._scriptSelect = 100; - scripts._scriptMoreFlag = 3; + _scriptSelect = 100; + _scriptMoreFlag = 3; } return; @@ -172,7 +210,7 @@ void Talk::talkTo(const Common::String &filename) { // If any sequences have changed in the prior talk file, restore them if (_savedSequences.size() > 0) { for (uint idx = 0; idx < _savedSequences.size(); ++idx) { - SavedSequence &ss = _savedSequences[idx]; + SequenceEntry &ss = _savedSequences[idx]; for (uint idx2 = 0; idx2 < _savedSequences.size(); ++idx2) scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2]; @@ -181,7 +219,7 @@ void Talk::talkTo(const Common::String &filename) { } } - while (!_sequenceStack.empty()) + while (!_scriptStack.empty()) pullSequence(); // Restore any pressed button @@ -276,7 +314,7 @@ void Talk::talkTo(const Common::String &filename) { select = _talkIndex = idx; } - if (scripts._scriptMoreFlag && _scriptSelect != 0) + if (_scriptMoreFlag && _scriptSelect != 0) select = _scriptSelect; if (select == -1) @@ -288,7 +326,7 @@ void Talk::talkTo(const Common::String &filename) { _talkHistory[_converseNum][select] = true; // Check if the talk file is meant to be a non-seen comment - if (filename[7] != '*') { + if (filename.size() < 8 || filename[7] != '*') { // Should we start in stealth mode? if (_statements[select]._statement.hasPrefix("^")) { _talkStealth = 2; @@ -305,13 +343,13 @@ void Talk::talkTo(const Common::String &filename) { // Handle replies until there's no further linked file, // or the link file isn't a reply first cnversation - for (;;) { + while (!_vm->shouldQuit()) { clearSequences(); _scriptSelect = select; _speaker = _talkTo; Statement &statement = _statements[select]; - scripts.doScript(_statements[select]._reply); + doScript(_statements[select]._reply); if (_talkToAbort) return; @@ -327,7 +365,7 @@ void Talk::talkTo(const Common::String &filename) { } // Check for a linked file - if (!statement._linkFile.empty() && !scripts._scriptMoreFlag) { + if (!statement._linkFile.empty() && !_scriptMoreFlag) { freeTalkVars(); loadTalkFile(statement._linkFile); @@ -422,7 +460,7 @@ void Talk::talkTo(const Common::String &filename) { // If a script was added to the script stack, restore state so that the // previous script can continue - if (!scripts._scriptStack.empty()) { + if (!_scriptStack.empty()) { scripts.popStack(); } @@ -547,7 +585,7 @@ void Talk::loadTalkFile(const Common::String &filename) { // Check for an existing person being talked to _talkTo = -1; for (int idx = 0; idx < MAX_PEOPLE; ++idx) { - if (scumm_strnicmp(filename.c_str(), people[(PeopleId)idx]._portrait.c_str(), 4)) { + if (!scumm_strnicmp(filename.c_str(), people[(PeopleId)idx]._portrait.c_str(), 4)) { _talkTo = idx; break; } @@ -568,7 +606,7 @@ void Talk::loadTalkFile(const Common::String &filename) { delete talkStream; - if (!sound._voicesOn) + if (!sound._voices) stripVoiceCommands(); setTalkMap(); } @@ -833,7 +871,7 @@ int Talk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt * Clears the stack of pending object sequences associated with speakers in the scene */ void Talk::clearSequences() { - _sequenceStack.clear(); + _scriptStack.clear(); } /** @@ -843,7 +881,7 @@ void Talk::clearSequences() { void Talk::pullSequence() { Scene &scene = *_vm->_scene; - SequenceEntry seq = _sequenceStack.pop(); + SequenceEntry seq = _scriptStack.pop(); if (seq._objNum != -1) { Object &obj = scene._bgShapes[seq._objNum]; @@ -871,7 +909,7 @@ void Talk::pushSequence(int speaker) { if (speaker == -1) return; - SequenceEntry seqEntry; + ScriptStackEntry seqEntry; if (!speaker) { seqEntry._objNum = -1; } else { @@ -885,9 +923,13 @@ void Talk::pushSequence(int speaker) { seqEntry._seqTo = obj._seqTo; } - _sequenceStack.push(seqEntry); - if (_sequenceStack.size() >= 5) - error("sequence stack overflow"); + _scriptStack.push(seqEntry); + if (_scriptStack.size() >= 5) + error("script stack overflow"); +} + +void Talk::setSequence(int speaker) { + // TODO } /** @@ -923,4 +965,751 @@ void Talk::setStillSeq(int speaker) { } } +/** + * Parses a reply for control codes and display text. The found text is printed within + * the text window, handles delays, animations, and animating portraits. + */ +void Talk::doScript(const Common::String &script) { + Animation &anim = *_vm->_animation; + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + 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 obj; + int seqCount; + + _saveSeqNum = 0; + + const char *str = script.c_str(); + if (_scriptMoreFlag) { + _scriptMoreFlag = 0; + str = script.c_str() + _scriptSaveIndex; + } + + // Check if the script begins with a Stealh Mode Active command + if (str[0] == STEALTH_MODE_ACTIVE || _talkStealth) { + _talkStealth = 2; + _speaker |= 128; + } else { + pushSequence(_speaker); + ui.clearWindow(); + + // Need to switch speakers? + if (str[0] == SWITCH_SPEAKER) { + _speaker = str[1] - 1; + str += 2; + pullSequence(); + pushSequence(_speaker); + setSequence(_speaker); + } + else { + setSequence(_speaker); + } + + // Assign portrait location? + if (str[0] == 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; + } + + // Remove portrait? + if (str[0] == REMOVE_PORTRAIT) { + _speaker = 255; + } + else { + // Nope, so set the first speaker + setTalking(_speaker); + } + } + + do { + Common::String tempString; + wait = 0; + + if (!str[0]) { + endStr = true; + } else if (str[0] == '{') { + // Start of comment, so skip over it + while (*str++ != '}') + ; + } else if (str[0] >= 128) { + // Handle control code + switch (str[0]) { + case SWITCH_SPEAKER: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + if (!(_speaker & 128)) + clearTalking(); + if (_talkToAbort) + return; + + ui.clearWindow(); + yp = CONTROLS_Y + 12; + charCount = line = 0; + + _speaker = *++str - 1; + setTalking(_speaker); + pullSequence(); + pushSequence(_speaker); + setSequence(_speaker); + break; + + case RUN_CANIMATION: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + scene.startCAnim((str[0] - 1) & 127, 1 + (str[0] & 128)); + if (_talkToAbort) + return; + + // Check if next character is changing side or changing portrait + if (charCount && (str[1] == SWITCH_SPEAKER || str[1] == ASSIGN_PORTRAIT_LOCATION)) + wait = 1; + break; + + case 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; + break; + + case PAUSE: + // Pause + charCount = *++str; + wait = pauseFlag = true; + break; + + case REMOVE_PORTRAIT: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + if (_speaker < 128) + clearTalking(); + pullSequence(); + if (_talkToAbort) + return; + + _speaker |= 128; + break; + + case CLEAR_WINDOW: + ui.clearWindow(); + yp = CONTROLS_Y + 12; + charCount = line = 0; + break; + + case 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 + obj = -1; + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + if (scumm_stricmp(tempString.c_str(), scene._bgShapes[idx]._name.c_str()) == 0) + obj = idx; + } + if (obj == -1) + error("Could not find object %s to change", tempString.c_str()); + + // Should the script be overwritten? + if (str[0] > 128) { + // Save the current sequence + _savedSequences.push(SequenceEntry()); + SequenceEntry &seqEntry = _savedSequences.top(); + seqEntry._objNum = obj; + seqEntry._seqTo = scene._bgShapes[obj]._seqTo; + for (uint idx = 0; idx < scene._bgShapes[obj]._seqSize; ++idx) + seqEntry._sequences.push_back(scene._bgShapes[obj]._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[obj]._sequences[idx] = str[0] - 1; + + // Reset object back to beginning of new sequence + scene._bgShapes[obj]._frameNumber = 0; + continue; + + case WALK_TO_COORDS: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100, str[2] * 100), str[3] - 1); + if (_talkToAbort) + return; + + str += 3; + break; + + case PAUSE_WITHOUT_CONTROL: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + for (int idx = 0; idx < (str[0] - 1); ++idx) { + scene.doBgAnim(); + if (_talkToAbort) + return; + + // Check for button press + events.pollEvents(); + events.setButtonState(); + } + break; + + case BANISH_WINDOW: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + if (!(_speaker & 128)) + clearTalking(); + pullSequence(); + + if (_talkToAbort) + return; + + _speaker |= 128; + ui.banishWindow(); + ui._menuMode = TALK_MODE; + noTextYet = true; + break; + + case 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"); + } + break; + + case SET_FLAG: { + ++str; + int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + int flag = (flag1 & 0x7fff) * (flag1 >= 0x8000 ? -1 : 1); + _vm->setFlags(flag); + ++str; + break; + } + + case SFX_COMMAND: + ++str; + if (sound._voices) { + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + sound.playSound(tempString); + + // Set voices to wait for more + sound._voices = 2; + sound._speechOn = (*sound._soundIsOn); + } + + wait = 1; + str += 7; + break; + + case TOGGLE_OBJECT: + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + + scene.toggleObject(tempString); + str += str[0]; + break; + + case STEALTH_MODE_ACTIVE: + _talkStealth = 2; + break; + + case 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] != ELSE_STATEMENT && str[0] != END_IF_STATEMENT); + + if (!str[0]) + endStr = true; + } + break; + } + + case 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] != END_IF_STATEMENT); + break; + + case STEALTH_MODE_DEACTIVATE: + _talkStealth = 0; + events.clearKeyboard(); + break; + + case TURN_HOLMES_OFF: + people._holmesOn = false; + break; + + case TURN_HOLMES_ON: + people._holmesOn = true; + break; + + case GOTO_SCENE: + scene._goToScene = str[1] - 1; + + if (scene._goToScene != 100) { + // Not going to the map overview + scene._oldCharPoint = scene._goToScene; + scene._overPos.x = _vm->_map[scene._goToScene].x * 100 - 600; + scene._overPos.y = _vm->_map[scene._goToScene].y * 100 + 900; + + // Run a canimation? + if (str[2] > 100) { + scene._hsavedFs = str[2]; + scene._hsavedPos = Common::Point(160, 100); + } + } + str += 6; + + _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; + _scriptSaveIndex = str - script.c_str(); + endStr = true; + wait = 0; + break; + + case PLAY_PROLOGUE: + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + + anim.playPrologue(tempString, 1, 3, true, 4); + break; + + case ADD_ITEM_TO_INVENTORY: + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx]; + str += str[0]; + + inv.putNameInInventory(tempString); + break; + + case SET_OBJECT: { + ++str; + for (int idx = 0; idx < (str[0] & 127); ++idx) + tempString += str[idx + 1]; + str += str[0]; + + // Set comparison state according to if we want to hide or unhide + bool state = (str[0] >= 128); + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + if (scumm_stricmp(tempString.c_str(), obj._name.c_str()) == 0) { + // Only toggle the object if it's not in the desired state already + if ((obj._type == HIDDEN && state) || (obj._type != HIDDEN && !state)) + obj.toggleHidden(); + } + } + break; + } + + case CALL_TALK_FILE: + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + str += 8; + + _scriptCurrentIndex = str - script.c_str(); + + // Save the current script position and new talk file + if (_scriptStack.size() < 10) { + ScriptStackEntry rec; + rec._name = _scriptName; + rec._currentIndex = _scriptCurrentIndex; + rec._select = _scriptSelect; + } else { + error("Script stack overflow"); + } + + _scriptMoreFlag = true; + endStr = true; + wait = 0; + break; + + case MOVE_MOUSE: + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); + if (_talkToAbort) + return; + str += 3; + break; + + case 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, tempString.c_str()); + break; + + case CLEAR_INFO_LINE: + ui._infoFlag = true; + ui.clearInfo(); + break; + + case WALK_TO_CANIMATION: { + ++str; + int animIndex = str[0] - 1; + + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = (str + 1) - script.c_str(); + if (_talkToAbort) + return; + break; + } + + case REMOVE_ITEM_FROM_INVENTORY: + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + inv.deleteItemFromInventory(tempString); + break; + + case ENABLE_END_KEY: + ui._endKeyActive = true; + break; + + case DISABLE_END_KEY: + ui._endKeyActive = false; + break; + + default: + break; + } + + ++str; + } else { + // If the window isn't yet open, draw the window before printing starts + if (!ui._windowOpen && noTextYet) { + noTextYet = false; + drawInterface(); + + if (_talkTo != -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"); + } + } + + // If it's the first line, display the speaker + if (!line && _speaker >= 0 && _speaker < MAX_PEOPLE) { + // 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, inv._names[_speaker & 127].c_str()); + } else { + screen.gPrint(Common::Point(16, yp - 1), TALK_FOREGROUND, inv._names[_speaker & 127].c_str()); + openTalkWindow = true; + } + + yp += 9; + } + + // Find amound of text that will fit on the line + int width = 0, idx = 0; + do { + width += screen.charWidth(str[idx]); + } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < 128); + + if (str[idx] || width >= 298) { + if (str[idx] < 128 && str[idx] != '{') { + --idx; + --charCount; + } + } else { + endStr = true; + } + + // If word wrap is needed, find the start of the current word + if (width >= 298) { + while (str[idx] != ' ') { + --idx; + --charCount; + } + } + + // Print the line + Common::String lineStr(str, str + idx); + + // If the speaker indicates a description file, print it in yellow + if (_speaker != -1) { + if (ui._windowOpen) { + screen.print(Common::Point(16, yp), INV_FOREGROUND, lineStr.c_str()); + } else { + screen.gPrint(Common::Point(16, yp - 1), INV_FOREGROUND, lineStr.c_str()); + } + } else { + if (ui._windowOpen) { + screen.print(Common::Point(16, yp), COMMAND_FOREGROUND, lineStr.c_str()); + } + else { + screen.gPrint(Common::Point(16, yp - 1), COMMAND_FOREGROUND, lineStr.c_str()); + } + } + + // Move to end of displayed line + str += idx; + + // If line wrap occurred, then move to after the separating space between the words + if (str[0] < 128 && str[0] != '{') + ++str; + + yp += 9; + ++line; + + // Certain different conditions require a wait + if ((line == 4 && str[0] != SFX_COMMAND && str[0] != PAUSE && _speaker != -1) || + (line == 5 && str[0] != PAUSE && _speaker != -1) || + endStr) { + wait = 1; + } + + switch (str[0]) { + case SWITCH_SPEAKER: + case ASSIGN_PORTRAIT_LOCATION: + case BANISH_WINDOW: + case IF_STATEMENT: + case ELSE_STATEMENT: + case END_IF_STATEMENT: + case GOTO_SCENE: + case CALL_TALK_FILE: + wait = 1; + break; + default: + break; + } + } + + // Open window if it wasn't already open, and text has already been printed + if ((openTalkWindow && wait) || (openTalkWindow && str[0] >= 128 && str[0] != COMMAND_161)) { + if (!ui._windowStyle) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + ui._windowOpen = true; + openTalkWindow = false; + } + + if (wait) { + // Save the current point in the script, since it might be intterupted by + // doing bg anims in the next call, so we need to know where to return to + _scriptCurrentIndex = str - script.c_str(); + + // Handling pausing + 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 (wait >= 0 && wait < 254) { + if (str[0] == SFX_COMMAND) + str += 9; + } + + // Clear the window unless the wait was due to a PAUSE command + if (!pauseFlag && wait != -1 && str[0] != SFX_COMMAND) { + if (!_talkStealth) + ui.clearWindow(); + yp = CONTROLS_Y + 12; + charCount = line = 0; + } + + pauseFlag = false; + } + } while (!_vm->shouldQuit() && !endStr); + + if (wait != -1) { + for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) { + SequenceEntry &seq = _savedSequences[ssIndex]; + Object &obj = scene._bgShapes[seq._objNum]; + + for (uint idx = 0; idx < seq._sequences.size(); ++idx) + obj._sequences[idx] = seq._sequences[idx]; + obj._frameNumber = seq._frameNumber; + obj._seqTo = seq._seqTo; + } + + pullSequence(); + if (_speaker < 128) + clearTalking(); + } +} + +void Talk::clearTalking() { + // TODO +} + +void Talk::setTalking(int speaker) { + // TODO +} + +/** + * When the talk window has been displayed, waits a period of time proportional to + * the amount of text that's been displayed + */ +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; + + // Unless we're in stealth mode, show the appropriate cursor + if (!_talkStealth) { + events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW); + } + + do { + if (sound._speechOn && !*sound._soundIsOn) + 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.setButtonState(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode >= 32 && keyState.keycode < 128) + key2 = keyState.keycode; + } + + if (_talkStealth) { + key2 = 254; + events._released = false; + } + } + + // Count down the delay + if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth) + --delay; + + // If there are voices playing, reset delay so that they keep playing + if (sound._voices == 2 && *sound._soundIsOn) + delay = 0; + } while (!_vm->shouldQuit() && key2 == 254 && (delay || (sound._voices == 2 && *sound._soundIsOn)) + && !events._released && !events._rightReleased); + + // If voices was set 2 to indicate a voice file was place, then reset it back to 1 + if (sound._voices == 2) + sound._voices = 1; + + if (delay > 0 && sound._diskSoundPlaying) + sound.stopSndFuncPtr(0, 0); + + // 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._speechOn = false; + events.setCursor(_talkToAbort ? ARROW : oldCursor); + events._pressed = events._released = false; + + return key2; +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h index 48cdd2b5b2..1b679a47bd 100644 --- a/engines/sherlock/talk.h +++ b/engines/sherlock/talk.h @@ -33,16 +33,19 @@ namespace Sherlock { #define MAX_TALK_SEQUENCES 11 -struct SavedSequence { +struct SequenceEntry { int _objNum; Common::Array _sequences; -}; - -struct SequenceEntry : public SavedSequence { int _frameNumber; int _seqTo; }; +struct ScriptStackEntry : public SequenceEntry { + Common::String _name; + int _currentIndex; + int _select; +}; + struct Statement { Common::String _statement; Common::String _reply; @@ -84,8 +87,8 @@ private: private: SherlockEngine *_vm; int _saveSeqNum; - Common::Array _savedSequences; - Common::Stack _sequenceStack; + Common::Stack _savedSequences; + Common::Stack _scriptStack; Common::Array _statements; TalkHistoryEntry _talkHistory[500]; int _speaker; @@ -95,17 +98,28 @@ private: int _talkStealth; int _talkToFlag; bool _moreTalkUp, _moreTalkDown; - + int _scriptSaveIndex; + int _scriptCurrentIndex; +private: void stripVoiceCommands(); void setTalkMap(); bool displayTalk(bool slamIt); int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt); + + void doScript(const Common::String &script); + + void clearTalking(); + void setTalking(int speaker); + + int waitForMore(int delay); public: bool _talkToAbort; int _talkCounter; int _talkTo; + int _scriptMoreFlag; + Common::String _scriptName; public: Talk(SherlockEngine *vm); void setSequences(const byte *talkSequences, const byte *stillSequences, @@ -127,7 +141,8 @@ public: void clearSequences(); void pullSequence(); void pushSequence(int speaker); - bool isSequencesEmpty() const { return _sequenceStack.empty(); } + void setSequence(int speaker); + bool isSequencesEmpty() const { return _scriptStack.empty(); } }; } // End of namespace Sherlock diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp index 289bff814f..88265f6a19 100644 --- a/engines/sherlock/user_interface.cpp +++ b/engines/sherlock/user_interface.cpp @@ -122,7 +122,6 @@ void UserInterface::handleInput() { People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; - Scripts &scripts = *_vm->_scripts; Talk &talk = *_vm->_talk; if (_menuCounter) @@ -148,7 +147,7 @@ void UserInterface::handleInput() { } // Do button highlighting check - if (!scripts._scriptMoreFlag) { // Don't if scripts are running + if (!talk._scriptMoreFlag) { // Don't if scripts are running if (((events._rightPressed || events._rightReleased) && _helpStyle) || (!_helpStyle && !_menuCounter)) { // Handle any default commands if we're in STD_MODE @@ -532,6 +531,13 @@ void UserInterface::examine() { _vm->setFlags(inv[_selector]._lookFlag); } + if (_invLookFlag) { + // Dont close the inventory window when starting an examine display, since it's + // window will slide up to replace the inventory display + _windowOpen = false; + _menuMode = LOOK_MODE; + } + if (!talk._talkToAbort) { if (!scene._cAnimFramePause) printObjectDesc(_cAnimStr, true); -- cgit v1.2.3