aboutsummaryrefslogtreecommitdiff
path: root/engines/sherlock
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sherlock')
-rw-r--r--engines/sherlock/sherlock.cpp2
-rw-r--r--engines/sherlock/talk.cpp1029
-rw-r--r--engines/sherlock/talk.h101
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