aboutsummaryrefslogtreecommitdiff
path: root/engines/sherlock
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sherlock')
-rw-r--r--engines/sherlock/animation.cpp324
-rw-r--r--engines/sherlock/animation.h86
-rw-r--r--engines/sherlock/configure.engine3
-rw-r--r--engines/sherlock/debugger.cpp138
-rw-r--r--engines/sherlock/debugger.h76
-rw-r--r--engines/sherlock/decompress.cpp128
-rw-r--r--engines/sherlock/detection.cpp245
-rw-r--r--engines/sherlock/detection_tables.h171
-rw-r--r--engines/sherlock/events.cpp318
-rw-r--r--engines/sherlock/events.h194
-rw-r--r--engines/sherlock/fixed_text.cpp38
-rw-r--r--engines/sherlock/fixed_text.h66
-rw-r--r--engines/sherlock/fonts.cpp204
-rw-r--r--engines/sherlock/fonts.h105
-rw-r--r--engines/sherlock/image_file.cpp1085
-rw-r--r--engines/sherlock/image_file.h207
-rw-r--r--engines/sherlock/inventory.cpp246
-rw-r--r--engines/sherlock/inventory.h150
-rw-r--r--engines/sherlock/journal.cpp706
-rw-r--r--engines/sherlock/journal.h105
-rw-r--r--engines/sherlock/map.cpp52
-rw-r--r--engines/sherlock/map.h61
-rw-r--r--engines/sherlock/module.mk71
-rw-r--r--engines/sherlock/music.cpp598
-rw-r--r--engines/sherlock/music.h131
-rw-r--r--engines/sherlock/objects.cpp1533
-rw-r--r--engines/sherlock/objects.h478
-rw-r--r--engines/sherlock/people.cpp332
-rw-r--r--engines/sherlock/people.h168
-rw-r--r--engines/sherlock/resources.cpp372
-rw-r--r--engines/sherlock/resources.h170
-rw-r--r--engines/sherlock/saveload.cpp278
-rw-r--r--engines/sherlock/saveload.h151
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.cpp510
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.h127
-rw-r--r--engines/sherlock/scalpel/darts.cpp553
-rw-r--r--engines/sherlock/scalpel/darts.h130
-rw-r--r--engines/sherlock/scalpel/drivers/adlib.cpp646
-rw-r--r--engines/sherlock/scalpel/drivers/mididriver.h41
-rw-r--r--engines/sherlock/scalpel/drivers/mt32.cpp282
-rw-r--r--engines/sherlock/scalpel/scalpel.cpp1160
-rw-r--r--engines/sherlock/scalpel/scalpel.h133
-rw-r--r--engines/sherlock/scalpel/scalpel_debugger.cpp91
-rw-r--r--engines/sherlock/scalpel/scalpel_debugger.h54
-rw-r--r--engines/sherlock/scalpel/scalpel_fixed_text.cpp377
-rw-r--r--engines/sherlock/scalpel/scalpel_fixed_text.h108
-rw-r--r--engines/sherlock/scalpel/scalpel_inventory.cpp296
-rw-r--r--engines/sherlock/scalpel/scalpel_inventory.h74
-rw-r--r--engines/sherlock/scalpel/scalpel_journal.cpp667
-rw-r--r--engines/sherlock/scalpel/scalpel_journal.h102
-rw-r--r--engines/sherlock/scalpel/scalpel_map.cpp596
-rw-r--r--engines/sherlock/scalpel/scalpel_map.h173
-rw-r--r--engines/sherlock/scalpel/scalpel_people.cpp532
-rw-r--r--engines/sherlock/scalpel/scalpel_people.h114
-rw-r--r--engines/sherlock/scalpel/scalpel_saveload.cpp261
-rw-r--r--engines/sherlock/scalpel/scalpel_saveload.h69
-rw-r--r--engines/sherlock/scalpel/scalpel_scene.cpp726
-rw-r--r--engines/sherlock/scalpel/scalpel_scene.h96
-rw-r--r--engines/sherlock/scalpel/scalpel_screen.cpp93
-rw-r--r--engines/sherlock/scalpel/scalpel_screen.h66
-rw-r--r--engines/sherlock/scalpel/scalpel_talk.cpp844
-rw-r--r--engines/sherlock/scalpel/scalpel_talk.h99
-rw-r--r--engines/sherlock/scalpel/scalpel_user_interface.cpp2189
-rw-r--r--engines/sherlock/scalpel/scalpel_user_interface.h223
-rw-r--r--engines/sherlock/scalpel/settings.cpp343
-rw-r--r--engines/sherlock/scalpel/settings.h63
-rw-r--r--engines/sherlock/scalpel/tsage/logo.cpp684
-rw-r--r--engines/sherlock/scalpel/tsage/logo.h250
-rw-r--r--engines/sherlock/scalpel/tsage/resources.cpp373
-rw-r--r--engines/sherlock/scalpel/tsage/resources.h139
-rw-r--r--engines/sherlock/scene.cpp1437
-rw-r--r--engines/sherlock/scene.h323
-rw-r--r--engines/sherlock/screen.cpp553
-rw-r--r--engines/sherlock/screen.h243
-rw-r--r--engines/sherlock/sherlock.cpp282
-rw-r--r--engines/sherlock/sherlock.h229
-rw-r--r--engines/sherlock/sound.cpp269
-rw-r--r--engines/sherlock/sound.h106
-rw-r--r--engines/sherlock/surface.cpp307
-rw-r--r--engines/sherlock/surface.h185
-rw-r--r--engines/sherlock/talk.cpp1297
-rw-r--r--engines/sherlock/talk.h381
-rw-r--r--engines/sherlock/tattoo/tattoo.cpp576
-rw-r--r--engines/sherlock/tattoo/tattoo.h127
-rw-r--r--engines/sherlock/tattoo/tattoo_darts.cpp967
-rw-r--r--engines/sherlock/tattoo/tattoo_darts.h172
-rw-r--r--engines/sherlock/tattoo/tattoo_debugger.cpp35
-rw-r--r--engines/sherlock/tattoo/tattoo_debugger.h44
-rw-r--r--engines/sherlock/tattoo/tattoo_fixed_text.cpp107
-rw-r--r--engines/sherlock/tattoo/tattoo_fixed_text.h114
-rw-r--r--engines/sherlock/tattoo/tattoo_inventory.cpp63
-rw-r--r--engines/sherlock/tattoo/tattoo_inventory.h48
-rw-r--r--engines/sherlock/tattoo/tattoo_journal.cpp900
-rw-r--r--engines/sherlock/tattoo/tattoo_journal.h103
-rw-r--r--engines/sherlock/tattoo/tattoo_map.cpp439
-rw-r--r--engines/sherlock/tattoo/tattoo_map.h93
-rw-r--r--engines/sherlock/tattoo/tattoo_people.cpp1432
-rw-r--r--engines/sherlock/tattoo/tattoo_people.h270
-rw-r--r--engines/sherlock/tattoo/tattoo_resources.cpp329
-rw-r--r--engines/sherlock/tattoo/tattoo_resources.h42
-rw-r--r--engines/sherlock/tattoo/tattoo_scene.cpp821
-rw-r--r--engines/sherlock/tattoo/tattoo_scene.h147
-rw-r--r--engines/sherlock/tattoo/tattoo_talk.cpp882
-rw-r--r--engines/sherlock/tattoo/tattoo_talk.h101
-rw-r--r--engines/sherlock/tattoo/tattoo_user_interface.cpp862
-rw-r--r--engines/sherlock/tattoo/tattoo_user_interface.h229
-rw-r--r--engines/sherlock/tattoo/widget_base.cpp299
-rw-r--r--engines/sherlock/tattoo/widget_base.h125
-rw-r--r--engines/sherlock/tattoo/widget_inventory.cpp762
-rw-r--r--engines/sherlock/tattoo/widget_inventory.h159
-rw-r--r--engines/sherlock/tattoo/widget_lab.cpp193
-rw-r--r--engines/sherlock/tattoo/widget_lab.h66
-rw-r--r--engines/sherlock/tattoo/widget_talk.cpp529
-rw-r--r--engines/sherlock/tattoo/widget_talk.h92
-rw-r--r--engines/sherlock/tattoo/widget_text.cpp223
-rw-r--r--engines/sherlock/tattoo/widget_text.h80
-rw-r--r--engines/sherlock/tattoo/widget_tooltip.cpp220
-rw-r--r--engines/sherlock/tattoo/widget_tooltip.h87
-rw-r--r--engines/sherlock/tattoo/widget_verbs.cpp311
-rw-r--r--engines/sherlock/tattoo/widget_verbs.h72
-rw-r--r--engines/sherlock/user_interface.cpp205
-rw-r--r--engines/sherlock/user_interface.h138
122 files changed, 40350 insertions, 0 deletions
diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp
new file mode 100644
index 0000000000..bbf7c913b7
--- /dev/null
+++ b/engines/sherlock/animation.cpp
@@ -0,0 +1,324 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/animation.h"
+#include "sherlock/sherlock.h"
+#include "common/algorithm.h"
+
+namespace Sherlock {
+
+static const int NO_FRAMES = FRAMES_END;
+
+Animation::Animation(SherlockEngine *vm) : _vm(vm) {
+}
+
+bool Animation::play(const Common::String &filename, bool intro, int minDelay, int fade,
+ bool setPalette, int speed) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Sound &sound = *_vm->_sound;
+ int soundNumber = 0;
+
+ // Check for any any sound frames for the given animation
+ const int *soundFrames = checkForSoundFrames(filename, intro);
+
+ // Add on the VDX extension
+ Common::String vdxName = filename + ".vdx";
+
+ // Load the animation
+ Common::SeekableReadStream *stream;
+ if (!_gfxLibraryFilename.empty())
+ stream = _vm->_res->load(vdxName, _gfxLibraryFilename);
+ else if (_vm->_useEpilogue2)
+ stream = _vm->_res->load(vdxName, "epilog2.lib");
+ else
+ stream = _vm->_res->load(vdxName, "epilogue.lib");
+
+ // Load initial image
+ Common::String vdaName = filename + ".vda";
+ ImageFile images(vdaName, true, true);
+
+ events.wait(minDelay);
+ if (fade != 0 && fade != 255)
+ screen.fadeToBlack();
+
+ if (setPalette) {
+ if (fade != 255)
+ screen.setPalette(images._palette);
+ }
+
+ int frameNumber = 0;
+ Common::Point pt;
+ bool skipped = false;
+ while (!_vm->shouldQuit()) {
+ // Get the next sprite to display
+ int imageFrame = stream->readSint16LE();
+
+ if (imageFrame == -2) {
+ // End of animation reached
+ break;
+ } else if (imageFrame != -1) {
+ // Read position from either animation stream or the sprite frame itself
+ if (imageFrame < 0) {
+ imageFrame += 32768;
+ pt.x = stream->readUint16LE();
+ pt.y = stream->readUint16LE();
+ } else {
+ pt = images[imageFrame]._offset;
+ }
+
+ // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame,
+ // since we don't want the offsets in the image file to be used, just the explicit position we specify
+ screen.transBlitFrom(images[imageFrame]._frame, pt);
+ } else {
+ // At this point, either the sprites for the frame has been complete, or there weren't any sprites
+ // at all to draw for the frame
+ if (fade == 255) {
+ // Gradual fade in
+ if (screen.equalizePalette(images._palette) == 0)
+ fade = 0;
+ }
+
+ // Check if we've reached a frame with sound
+ if (frameNumber++ == *soundFrames) {
+ ++soundNumber;
+ ++soundFrames;
+
+ Common::String sampleFilename;
+
+ if (!intro) {
+ // regular animation, append 1-digit number
+ sampleFilename = Common::String::format("%s%01d", filename.c_str(), soundNumber);
+ } else {
+ // intro animation, append 2-digit number
+ sampleFilename = Common::String::format("%s%02d", filename.c_str(), soundNumber);
+ }
+
+ if (sound._voices)
+ sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str());
+ }
+
+ events.wait(speed * 3);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ if (keyState.keycode == Common::KEYCODE_ESCAPE ||
+ keyState.keycode == Common::KEYCODE_SPACE) {
+ skipped = true;
+ break;
+ }
+ } else if (events._pressed) {
+ skipped = true;
+ break;
+ }
+ }
+
+ events.clearEvents();
+ sound.stopSound();
+ delete stream;
+
+ return !skipped && !_vm->shouldQuit();
+}
+
+bool Animation::play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey,
+ int speed) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Sound &sound = *_vm->_sound;
+ int soundNumber = 0;
+
+ bool fadeActive = false;
+ uint16 fadeLimitColor = 0;
+ uint16 fadeLimitColorRed = 0;
+ uint16 fadeLimitColorGreen = 0;
+ uint16 fadeLimitColorBlue = 0;
+
+ // Check for any any sound frames for the given animation
+ const int *soundFrames = checkForSoundFrames(filename, intro);
+
+ // Add the VDX extension
+ Common::String indexName = "prologue/" + filename + ".3dx";
+
+ // Load the animation
+ Common::File *indexStream = new Common::File();
+
+ if (!indexStream->open(indexName)) {
+ warning("unable to open %s\n", indexName.c_str());
+ return false;
+ }
+
+ // Load initial image
+ Common::String graphicsName = "prologue/" + filename + ".3da";
+ ImageFile3DO images(graphicsName, kImageFile3DOType_Animation);
+
+ events.wait(minDelay);
+
+ if (fadeFromGrey) {
+ fadeActive = true;
+ fadeLimitColor = 0xCE59; // RGB565: 25, 50, 25 -> "grey"
+ }
+
+ int frameNumber = 0;
+ Common::Point pt;
+ bool skipped = false;
+ while (!_vm->shouldQuit()) {
+ // Get the next sprite to display
+ int imageFrame = indexStream->readSint16BE();
+
+ if (imageFrame == -2) {
+ // End of animation reached
+ break;
+ } else if (imageFrame != -1) {
+ // Read position from either animation stream or the sprite frame itself
+ if (imageFrame < 0) {
+ imageFrame += 32768;
+ pt.x = indexStream->readUint16BE();
+ pt.y = indexStream->readUint16BE();
+ } else {
+ pt = images[imageFrame]._offset;
+ }
+
+ // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame,
+ // since we don't want the offsets in the image file to be used, just the explicit position we specify
+ if (!fadeActive) {
+ screen.transBlitFrom(images[imageFrame]._frame, pt);
+ } else {
+ // Fade active, blit to backbuffer1
+ screen._backBuffer1.transBlitFrom(images[imageFrame]._frame, pt);
+ }
+ } else {
+ // At this point, either the sprites for the frame has been complete, or there weren't any sprites
+ // at all to draw for the frame
+
+ if (fadeActive) {
+ // process fading
+ screen.blitFrom3DOcolorLimit(fadeLimitColor);
+
+ if (!fadeLimitColor) {
+ // we are at the end, so stop
+ fadeActive = false;
+ } else {
+ // decrease limit color
+ fadeLimitColorRed = fadeLimitColor & 0xF800;
+ fadeLimitColorGreen = fadeLimitColor & 0x07E0;
+ fadeLimitColorBlue = fadeLimitColor & 0x001F;
+ if (fadeLimitColorRed)
+ fadeLimitColor -= 0x0800;
+ if (fadeLimitColorGreen)
+ fadeLimitColor -= 0x0040; // -2 because we are using RGB565, sherlock uses RGB555
+ if (fadeLimitColorBlue)
+ fadeLimitColor -= 0x0001;
+ }
+ }
+
+ // Check if we've reached a frame with sound
+ if (frameNumber++ == *soundFrames) {
+ ++soundNumber;
+ ++soundFrames;
+
+ Common::String sampleFilename;
+
+ // append 1-digit number
+ sampleFilename = Common::String::format("prologue/sounds/%s%01d", filename.c_str(), soundNumber);
+
+ if (sound._voices)
+ sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100); // no sound library
+ }
+ events.wait(speed * 3);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ if (keyState.keycode == Common::KEYCODE_ESCAPE ||
+ keyState.keycode == Common::KEYCODE_SPACE) {
+ skipped = true;
+ break;
+ }
+ } else if (events._pressed) {
+ skipped = true;
+ break;
+ }
+ }
+
+ events.clearEvents();
+ sound.stopSound();
+ delete indexStream;
+
+ return !skipped && !_vm->shouldQuit();
+}
+
+void Animation::setPrologueNames(const char *const *names, int count) {
+ for (int idx = 0; idx < count; ++idx, ++names) {
+ _prologueNames.push_back(*names);
+ }
+}
+
+void Animation::setPrologueFrames(const int *frames, int count, int maxFrames) {
+ _prologueFrames.resize(count);
+
+ for (int idx = 0; idx < count; ++idx, frames += maxFrames) {
+ _prologueFrames[idx].resize(maxFrames);
+ Common::copy(frames, frames + maxFrames, &_prologueFrames[idx][0]);
+ }
+}
+
+void Animation::setTitleNames(const char *const *names, int count) {
+ for (int idx = 0; idx < count; ++idx, ++names) {
+ _titleNames.push_back(*names);
+ }
+}
+
+void Animation::setTitleFrames(const int *frames, int count, int maxFrames) {
+ _titleFrames.resize(count);
+
+ for (int idx = 0; idx < count; ++idx, frames += maxFrames) {
+ _titleFrames[idx].resize(maxFrames);
+ Common::copy(frames, frames + maxFrames, &_titleFrames[idx][0]);
+ }
+}
+
+const int *Animation::checkForSoundFrames(const Common::String &filename, bool intro) {
+ const int *frames = &NO_FRAMES;
+
+ if (!intro) {
+ // regular animation is playing
+ for (uint idx = 0; idx < _prologueNames.size(); ++idx) {
+ if (filename.equalsIgnoreCase(_prologueNames[idx])) {
+ frames = &_prologueFrames[idx][0];
+ break;
+ }
+ }
+ } else {
+ // intro-animation is playing
+ for (uint idx = 0; idx < _titleNames.size(); ++idx) {
+ if (filename.equalsIgnoreCase(_titleNames[idx])) {
+ frames = &_titleFrames[idx][0];
+ break;
+ }
+ }
+ }
+
+ return frames;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/animation.h b/engines/sherlock/animation.h
new file mode 100644
index 0000000000..f3c95d4027
--- /dev/null
+++ b/engines/sherlock/animation.h
@@ -0,0 +1,86 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_ANIMATION_H
+#define SHERLOCK_ANIMATION_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "common/array.h"
+
+namespace Sherlock {
+
+#define FRAMES_END 32000
+
+class SherlockEngine;
+
+class Animation {
+private:
+ SherlockEngine *_vm;
+
+ Common::Array<const char *> _prologueNames;
+ Common::Array<Common::Array<int> > _prologueFrames;
+ Common::Array<const char *> _titleNames;
+ Common::Array<Common::Array<int> > _titleFrames;
+
+ /**
+ * Checks for whether an animation is being played that has associated sound
+ */
+ const int *checkForSoundFrames(const Common::String &filename, bool intro);
+public:
+ Common::String _soundLibraryFilename;
+ Common::String _gfxLibraryFilename;
+
+public:
+ Animation(SherlockEngine *vm);
+
+ /**
+ * Load the prologue name array
+ */
+ void setPrologueNames(const char *const *names, int count);
+
+ /**
+ * Load the prologue frame array
+ */
+ void setPrologueFrames(const int *frames, int count, int maxFrames);
+
+ /**
+ * Load the title name array
+ */
+ void setTitleNames(const char *const *names, int count);
+
+ /**
+ * Load the title frame array
+ */
+ void setTitleFrames(const int *frames, int count, int maxFrames);
+
+ /**
+ * Play a full-screen animation
+ */
+ bool play(const Common::String &filename, bool intro, int minDelay, int fade, bool setPalette, int speed);
+
+ bool play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, int speed);
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/configure.engine b/engines/sherlock/configure.engine
new file mode 100644
index 0000000000..a56129a8f0
--- /dev/null
+++ b/engines/sherlock/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine sherlock "The Lost Files of Sherlock Holmes" no
diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp
new file mode 100644
index 0000000000..2813a7eb69
--- /dev/null
+++ b/engines/sherlock/debugger.cpp
@@ -0,0 +1,138 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/debugger.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/music.h"
+#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "sherlock/scalpel/scalpel_debugger.h"
+#include "sherlock/tattoo/tattoo_debugger.h"
+#include "audio/mixer.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+
+namespace Sherlock {
+
+Debugger *Debugger::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_RoseTattoo)
+ return new Tattoo::TattooDebugger(vm);
+ else
+ return new Scalpel::ScalpelDebugger(vm);
+}
+
+Debugger::Debugger(SherlockEngine *vm) : GUI::Debugger(), _vm(vm) {
+ _showAllLocations = LOC_DISABLED;
+
+ registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
+ registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
+ registerCmd("song", WRAP_METHOD(Debugger, cmdSong));
+ registerCmd("dumpfile", WRAP_METHOD(Debugger, cmdDumpFile));
+ registerCmd("locations", WRAP_METHOD(Debugger, cmdLocations));
+}
+
+void Debugger::postEnter() {
+ if (!_3doPlayMovieFile.empty()) {
+ Scalpel3DOMoviePlay(_3doPlayMovieFile.c_str(), Common::Point(0, 0));
+
+ _3doPlayMovieFile.clear();
+ }
+
+ _vm->pauseEngine(false);
+}
+
+int Debugger::strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp = 0;
+ int read = sscanf(s, "%xh", &tmp);
+ if (read < 1)
+ error("strToInt failed on string \"%s\"", s);
+ return (int)tmp;
+}
+
+bool Debugger::cmdScene(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: scene <room>\n");
+ return true;
+ } else {
+ _vm->_scene->_goToScene = strToInt(argv[1]);
+ return false;
+ }
+}
+
+bool Debugger::cmdSong(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: song <room>\n");
+ return true;
+ }
+
+ if (!_vm->_music->loadSong(strToInt(argv[1]))) {
+ debugPrintf("Invalid song number.\n");
+ return true;
+ }
+ return false;
+}
+
+bool Debugger::cmdDumpFile(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: dumpfile <resource name>\n");
+ return true;
+ }
+
+ Common::SeekableReadStream *s = _vm->_res->load(argv[1]);
+ if (!s) {
+ debugPrintf("Invalid resource.\n");
+ return true;
+ }
+
+ byte *buffer = new byte[s->size()];
+ s->read(buffer, s->size());
+
+ Common::DumpFile dumpFile;
+ dumpFile.open(argv[1]);
+
+ dumpFile.write(buffer, s->size());
+ dumpFile.flush();
+ dumpFile.close();
+
+ delete[] buffer;
+
+ debugPrintf("Resource %s has been dumped to disk.\n", argv[1]);
+
+ return true;
+}
+
+bool Debugger::cmdLocations(int argc, const char **argv) {
+ _showAllLocations = LOC_REFRESH;
+
+ debugPrintf("Now showing all map locations\n");
+ return false;
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/debugger.h b/engines/sherlock/debugger.h
new file mode 100644
index 0000000000..abc8ef012d
--- /dev/null
+++ b/engines/sherlock/debugger.h
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_DEBUGGER_H
+#define SHERLOCK_DEBUGGER_H
+
+#include "common/scummsys.h"
+#include "gui/debugger.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+enum AllLocations { LOC_REFRESH = -1, LOC_DISABLED = 0, LOC_ALL = 1 };
+
+class Debugger : public GUI::Debugger {
+private:
+ /**
+ * Converts a decimal or hexadecimal string into a number
+ */
+ int strToInt(const char *s);
+
+ /**
+ * Switch to another scene
+ */
+ bool cmdScene(int argc, const char **argv);
+
+ /**
+ * Plays a song
+ */
+ bool cmdSong(int argc, const char **argv);
+
+ /**
+ * Dumps a file to disk
+ */
+ bool cmdDumpFile(int argc, const char **argv);
+
+ /**
+ * Show all locations on the map
+ */
+ bool cmdLocations(int argc, const char **argv);
+protected:
+ SherlockEngine *_vm;
+ Common::String _3doPlayMovieFile;
+public:
+ AllLocations _showAllLocations;
+public:
+ Debugger(SherlockEngine *vm);
+ virtual ~Debugger() {}
+ static Debugger *init(SherlockEngine *vm);
+
+ void postEnter();
+};
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_DEBUGGER_H */
diff --git a/engines/sherlock/decompress.cpp b/engines/sherlock/decompress.cpp
new file mode 100644
index 0000000000..8e02da3212
--- /dev/null
+++ b/engines/sherlock/decompress.cpp
@@ -0,0 +1,128 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/decompress.h"
+
+namespace Sherlock {
+
+/**
+ * Decompresses an LZW compressed resource. If no outSize is specified, it will
+ * decompress the entire resource. If, however, an explicit size is specified,
+ * then it means we're already within a resource, and only want to decompress
+ * part of it.
+ */
+Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, int32 outSize) {
+ if (outSize == -1) {
+ source.seek(5);
+ outSize = source.readSint32LE();
+ }
+
+ byte lzWindow[4096];
+ uint16 lzWindowPos;
+ uint16 cmd;
+
+ byte *outBuffer = new byte[outSize];
+ byte *outBufferEnd = outBuffer + outSize;
+ Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES);
+
+ memset(lzWindow, 0xFF, 0xFEE);
+ lzWindowPos = 0xFEE;
+ cmd = 0;
+
+ do {
+ cmd >>= 1;
+ if (!(cmd & 0x100))
+ cmd = source.readByte() | 0xFF00;
+
+ if (cmd & 1) {
+ byte literal = source.readByte();
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ } else {
+ int copyPos, copyLen;
+ copyPos = source.readByte();
+ copyLen = source.readByte();
+ copyPos = copyPos | ((copyLen & 0xF0) << 4);
+ copyLen = (copyLen & 0x0F) + 3;
+ while (copyLen--) {
+ byte literal = lzWindow[copyPos];
+ copyPos = (copyPos + 1) & 0x0FFF;
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ }
+ }
+ } while (outBuffer < outBufferEnd);
+
+ return outS;
+}
+
+
+/**
+ * Decompresses a Rose Tattoo resource
+ *
+Common::SeekableReadStream *decompress32(Common::SeekableReadStream &source, int32 outSize) {
+ if (outSize == -1) {
+ outSize = source.readSint32LE();
+ }
+
+ byte lzWindow[8192];
+ byte *outBuffer = new byte[outSize];
+ byte *outBufferEnd = outBuffer + outSize;
+ Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES);
+
+ memset(lzWindow, 0xFF, 8192);
+ int lzWindowPos = 0xFEE;
+ int cmd = 0;
+
+ do {
+ cmd >>= 1;
+ if (!(cmd & 0x100))
+ cmd = source.readByte() | 0xFF00;
+
+ if (cmd & 1) {
+ byte literal = source.readByte();
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ } else {
+ int copyPos, copyLen;
+ copyPos = source.readByte();
+ copyLen = source.readByte();
+ copyPos = copyPos | ((copyLen & 0xF0) << 4);
+ copyLen = (copyLen & 0x0F) + 3;
+ while (copyLen--) {
+ byte literal = lzWindow[copyPos];
+ copyPos = (copyPos + 1) & 0x0FFF;
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ }
+ }
+ } while (outBuffer < outBufferEnd);
+
+ return outS;
+}
+*/
+
+} // namespace Sherlock
diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp
new file mode 100644
index 0000000000..35a810efb1
--- /dev/null
+++ b/engines/sherlock/detection.cpp
@@ -0,0 +1,245 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/saveload.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "common/system.h"
+#include "common/translation.h"
+#include "engines/advancedDetector.h"
+
+namespace Sherlock {
+
+struct SherlockGameDescription {
+ ADGameDescription desc;
+
+ GameType gameID;
+};
+
+GameType SherlockEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+Common::Platform SherlockEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+Common::Language SherlockEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+} // End of namespace Sherlock
+
+static const PlainGameDescriptor sherlockGames[] = {
+ { "scalpel", "The Case of the Serrated Scalpel" },
+ { "rosetattoo", "The Case of the Rose Tattoo" },
+ {0, 0}
+};
+
+
+#define GAMEOPTION_ORIGINAL_SAVES GUIO_GAMEOPTIONS1
+#define GAMEOPTION_FADE_STYLE GUIO_GAMEOPTIONS2
+#define GAMEOPTION_HELP_STYLE GUIO_GAMEOPTIONS3
+#define GAMEOPTION_PORTRAITS_ON GUIO_GAMEOPTIONS4
+#define GAMEOPTION_WINDOW_STYLE GUIO_GAMEOPTIONS5
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ {
+ GAMEOPTION_ORIGINAL_SAVES,
+ {
+ _s("Use original savegame dialog"),
+ _s("Files button in-game shows original savegame dialog rather than the ScummVM menu"),
+ "originalsaveload",
+ false
+ }
+ },
+
+ {
+ GAMEOPTION_FADE_STYLE,
+ {
+ _s("Pixellated scene transitions"),
+ _s("When changing scenes, a randomized pixel transition is done"),
+ "fade_style",
+ true
+ }
+ },
+
+ {
+ GAMEOPTION_HELP_STYLE,
+ {
+ _s("Don't show hotspots when moving mouse"),
+ _s("Only show hotspot names after you actually click on a hotspot or action button"),
+ "help_style",
+ false
+ }
+ },
+
+ {
+ GAMEOPTION_PORTRAITS_ON,
+ {
+ _s("Show character portraits"),
+ _s("Show portraits for the characters when conversing"),
+ "portraits_on",
+ true
+ }
+ },
+
+ {
+ GAMEOPTION_WINDOW_STYLE,
+ {
+ _s("Slide dialogs into view"),
+ _s("Slide UI dialogs into view, rather than simply showing them immediately"),
+ "window_style",
+ true
+ }
+ },
+
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+
+#include "sherlock/detection_tables.h"
+
+class SherlockMetaEngine : public AdvancedMetaEngine {
+public:
+ SherlockMetaEngine() : AdvancedMetaEngine(Sherlock::gameDescriptions, sizeof(Sherlock::SherlockGameDescription),
+ sherlockGames, optionsList) {}
+
+ virtual const char *getName() const {
+ return "Sherlock Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Sherlock Engine (C) 1992-1996 Mythos Software, 1992-1996 (C) Electronic Arts";
+ }
+
+ /**
+ * Creates an instance of the game engine
+ */
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+
+ /**
+ * Returns a list of features the game's MetaEngine support
+ */
+ virtual bool hasFeature(MetaEngineFeature f) const;
+
+ /**
+ * Return a list of savegames
+ */
+ virtual SaveStateList listSaves(const char *target) const;
+
+ /**
+ * Returns the maximum number of allowed save slots
+ */
+ virtual int getMaximumSaveSlot() const;
+
+ /**
+ * Deletes a savegame in the specified slot
+ */
+ virtual void removeSaveState(const char *target, int slot) const;
+
+ /**
+ * Given a specified savegame slot, returns extended information for the save
+ */
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool SherlockMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const Sherlock::SherlockGameDescription *gd = (const Sherlock::SherlockGameDescription *)desc;
+ if (gd) {
+ switch (gd->gameID) {
+ case Sherlock::GType_SerratedScalpel:
+ *engine = new Sherlock::Scalpel::ScalpelEngine(syst, gd);
+ break;
+ case Sherlock::GType_RoseTattoo:
+ *engine = new Sherlock::Tattoo::TattooEngine(syst, gd);
+ break;
+ default:
+ error("Unknown game");
+ break;
+ }
+ }
+ return gd != 0;
+}
+
+bool SherlockMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail);
+}
+
+bool Sherlock::SherlockEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+bool Sherlock::SherlockEngine::isDemo() const {
+ return _gameDescription->desc.flags & ADGF_DEMO;
+}
+
+SaveStateList SherlockMetaEngine::listSaves(const char *target) const {
+ return Sherlock::SaveManager::getSavegameList(target);
+}
+
+int SherlockMetaEngine::getMaximumSaveSlot() const {
+ return MAX_SAVEGAME_SLOTS;
+}
+
+void SherlockMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::String filename = Sherlock::SaveManager(nullptr, target).generateSaveName(slot);
+ g_system->getSavefileManager()->removeSavefile(filename);
+}
+
+SaveStateDescriptor SherlockMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Sherlock::SaveManager(nullptr, target).generateSaveName(slot);
+ Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
+
+ if (f) {
+ Sherlock::SherlockSavegameHeader header;
+ Sherlock::SaveManager::readSavegameHeader(f, header);
+ delete f;
+
+ // Create the return descriptor
+ SaveStateDescriptor desc(slot, header._saveName);
+ desc.setThumbnail(header._thumbnail);
+ desc.setSaveDate(header._year, header._month, header._day);
+ desc.setSaveTime(header._hour, header._minute);
+ desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME);
+
+ return desc;
+ }
+
+ return SaveStateDescriptor();
+}
+
+
+#if PLUGIN_ENABLED_DYNAMIC(SHERLOCK)
+ REGISTER_PLUGIN_DYNAMIC(SHERLOCK, PLUGIN_TYPE_ENGINE, SherlockMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(SHERLOCK, PLUGIN_TYPE_ENGINE, SherlockMetaEngine);
+#endif
diff --git a/engines/sherlock/detection_tables.h b/engines/sherlock/detection_tables.h
new file mode 100644
index 0000000000..991fc2055d
--- /dev/null
+++ b/engines/sherlock/detection_tables.h
@@ -0,0 +1,171 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+namespace Sherlock {
+
+static const SherlockGameDescription gameDescriptions[] = {
+ {
+ // Case of the Serrated Scalpel - English 3.5" Floppy
+ // The HitSquad CD version has the same MD5
+ {
+ "scalpel",
+ 0,
+ AD_ENTRY1s("talk.lib", "ad0c4d6865edf15da4e9204c08815875", 238928),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - German CD (from multilingual CD)
+ // Provided by m_kiewitz
+ {
+ "scalpel",
+ 0, {
+ {"talk.lib", 0, "40a5f9f37c0e0d2ad48d8f44d8e393c9", 284278},
+ {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465},
+ AD_LISTEND},
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - Spanish CD (from multilingual CD)
+ // Provided by m_kiewitz
+ {
+ "scalpel",
+ 0, {
+ {"talk.lib", 0, "27697804b637a7f3b77234bf16f15dce", 171419},
+ {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465},
+ AD_LISTEND},
+ Common::ES_ESP,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - English 3DO
+ {
+ "scalpel",
+ 0,
+ AD_ENTRY1s("talk.lib", "20f74a29f2db6475e85b029ac9fc03bc", 240610),
+ Common::EN_ANY,
+ Common::kPlatform3DO,
+ ADGF_UNSTABLE,
+ GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE,
+ GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - Interactive English Demo
+ // Provided by Strangerke
+ {
+ "scalpel",
+ "Interactive Demo",
+ AD_ENTRY1s("talk.lib", "dbdc8a20c96900aa7e4d02f3fe8a274c", 121102),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE | ADGF_DEMO,
+ GUIO1(GUIO_NOSPEECH)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Serrated Scalpel - Non-Interactive English Demo
+ // Provided by Strangerke
+ {
+ "scalpel",
+ "Non Interactive Demo",
+ AD_ENTRY1s("music.lib", "ec19a09b7fef6fd90b1ab812ce6e9739", 38563),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE | ADGF_DEMO,
+ GUIO1(GUIO_NOSPEECH)
+ },
+ GType_SerratedScalpel,
+ },
+
+ {
+ // Case of the Rose Tattoo - French CD
+ // Provided by Strangerke
+ {
+ "rosetattoo",
+ "CD",
+ AD_ENTRY1s("talk.lib", "22e8e6406dd2fbbb238c9898928df42e", 770756),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GType_RoseTattoo
+ },
+
+ {
+ // Case of the Rose Tattoo - English CD
+ // Provided by dreammaster
+ {
+ "rosetattoo",
+ "CD",
+ AD_ENTRY1s("talk.lib", "9639a756b0993ebd71cb5f4d8b78b2dc", 765134),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GType_RoseTattoo,
+ },
+
+ {
+ // Case of the Rose Tattoo - German CD
+ // Provided by m_kiewitz
+ {
+ "rosetattoo",
+ "CD",
+ AD_ENTRY1s("talk.lib", "5027aa72f0d263ed3b1c764a6c397911", 873864),
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO0()
+ },
+ GType_RoseTattoo,
+ },
+
+ { AD_TABLE_END_MARKER, (GameType)0 }
+};
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp
new file mode 100644
index 0000000000..a8912f6f1e
--- /dev/null
+++ b/engines/sherlock/events.cpp
@@ -0,0 +1,318 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "engines/util.h"
+#include "graphics/cursorman.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/events.h"
+
+namespace Sherlock {
+
+enum ButtonFlag { LEFT_BUTTON = 1, RIGHT_BUTTON = 2 };
+
+Events::Events(SherlockEngine *vm): _vm(vm) {
+ _cursorImages = nullptr;
+ _cursorId = INVALID_CURSOR;
+ _frameCounter = 1;
+ _priorFrameTime = 0;
+ _mouseButtons = 0;
+ _pressed = _released = false;
+ _rightPressed = _rightReleased = false;
+ _oldButtons = _oldRightButton = false;
+ _firstPress = false;
+
+ if (_vm->_interactiveFl)
+ loadCursors("rmouse.vgs");
+}
+
+Events::~Events() {
+ delete _cursorImages;
+}
+
+void Events::loadCursors(const Common::String &filename) {
+ hideCursor();
+ delete _cursorImages;
+
+ if (!IS_3DO) {
+ // PC
+ _cursorImages = new ImageFile(filename);
+ } else {
+ // 3DO
+ _cursorImages = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat);
+ }
+ _cursorId = INVALID_CURSOR;
+}
+
+void Events::setCursor(CursorId cursorId) {
+ if (cursorId == _cursorId)
+ return;
+
+ int hotspotX, hotspotY;
+
+ if (cursorId == MAGNIFY) {
+ hotspotX = 8;
+ hotspotY = 8;
+ } else {
+ hotspotX = 0;
+ hotspotY = 0;
+ }
+
+ // Set the cursor data
+ Graphics::Surface &s = (*_cursorImages)[cursorId]._frame;
+
+ setCursor(s, hotspotX, hotspotY);
+
+ _cursorId = cursorId;
+}
+
+void Events::setCursor(const Graphics::Surface &src, int hotspotX, int hotspotY) {
+ _cursorId = INVALID_CURSOR;
+ _hotspotPos = Common::Point(hotspotX, hotspotY);
+
+ if (!IS_3DO) {
+ // PC 8-bit palettized
+ CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0xff);
+ } else {
+ // 3DO RGB565
+ CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0x0000, false, &src.format);
+ }
+ showCursor();
+}
+
+void Events::setCursor(CursorId cursorId, const Graphics::Surface &surface) {
+ _cursorId = cursorId;
+
+ int hotspotX, hotspotY;
+ if (cursorId == MAGNIFY) {
+ hotspotX = 8;
+ hotspotY = 8;
+ } else {
+ hotspotX = 0;
+ hotspotY = 0;
+ }
+
+ // Get the standard cursor frame
+ Graphics::Surface &surface2 = (*_cursorImages)[cursorId]._frame;
+
+ // Form a single surface containing both frames
+ int maxWidth = MAX(surface.w, surface2.w);
+ Graphics::Surface s;
+ s.create(maxWidth, surface.h + surface2.h, Graphics::PixelFormat::createFormatCLUT8());
+ s.fillRect(Common::Rect(0, 0, maxWidth, surface.h + surface2.h), TRANSPARENCY);
+
+ s.copyRectToSurface(surface, (maxWidth - surface.w) / 2, 0, Common::Rect(0, 0, surface.w, surface.h));
+ s.copyRectToSurface(surface2, (maxWidth - surface2.w) / 2, surface.h, Common::Rect(0, 0, surface2.w, surface2.h));
+
+ // Adjust hotspot position
+ hotspotX += (maxWidth - surface2.w) / 2;
+ hotspotY += surface.h;
+
+ // Set the cursor
+ setCursor(s, hotspotX, hotspotY);
+}
+
+void Events::animateCursorIfNeeded() {
+ if (_cursorId >= WAIT && _cursorId < (WAIT + 3)) {
+ CursorId newId = (_cursorId == WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1);
+ setCursor(newId);
+ }
+}
+
+
+void Events::showCursor() {
+ CursorMan.showMouse(true);
+}
+
+void Events::hideCursor() {
+ CursorMan.showMouse(false);
+}
+
+CursorId Events::getCursor() const {
+ return _cursorId;
+}
+
+bool Events::isCursorVisible() const {
+ return CursorMan.isVisible();
+}
+
+void Events::moveMouse(const Common::Point &pt) {
+ g_system->warpMouse(pt.x, pt.y);
+}
+
+void Events::pollEvents() {
+ checkForNextFrameCounter();
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ // Handle keypress
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RTL:
+ return;
+
+ case Common::EVENT_KEYDOWN:
+ // Check for debugger
+ if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) {
+ // Attach to the debugger
+ _vm->_debugger->attach();
+ _vm->_debugger->onFrame();
+ } else {
+ _pendingKeys.push(event.kbd);
+ }
+ return;
+ case Common::EVENT_KEYUP:
+ return;
+ case Common::EVENT_LBUTTONDOWN:
+ _mouseButtons |= LEFT_BUTTON;
+ return;
+ case Common::EVENT_RBUTTONDOWN:
+ _mouseButtons |= RIGHT_BUTTON;
+ return;
+ case Common::EVENT_LBUTTONUP:
+ _mouseButtons &= ~LEFT_BUTTON;
+ return;
+ case Common::EVENT_RBUTTONUP:
+ _mouseButtons &= ~RIGHT_BUTTON;
+ return;
+ default:
+ break;
+ }
+ }
+}
+
+void Events::pollEventsAndWait() {
+ pollEvents();
+ g_system->delayMillis(10);
+}
+
+void Events::warpMouse(const Common::Point &pt) {
+ g_system->warpMouse(pt.x, pt.y);
+}
+
+bool Events::checkForNextFrameCounter() {
+ // Check for next game frame
+ uint32 milli = g_system->getMillis();
+ if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) {
+ ++_frameCounter;
+ _priorFrameTime = milli;
+
+ // Give time to the debugger
+ _vm->_debugger->onFrame();
+
+ // Display the frame
+ _vm->_screen->update();
+
+ return true;
+ }
+
+ return false;
+}
+
+Common::Point Events::screenMousePos() const {
+ return g_system->getEventManager()->getMousePos();
+}
+
+Common::Point Events::mousePos() const {
+ return screenMousePos() + _vm->_screen->_currentScroll;
+}
+
+Common::KeyState Events::getKey() {
+ return _pendingKeys.pop();
+}
+
+void Events::clearEvents() {
+ _pendingKeys.clear();
+ _mouseButtons = 0;
+ _pressed = _released = false;
+ _rightPressed = _rightReleased = false;
+ _oldButtons = _oldRightButton = false;
+ _firstPress = false;
+}
+
+void Events::clearKeyboard() {
+ _pendingKeys.clear();
+}
+
+void Events::wait(int numFrames) {
+ uint32 totalMilli = numFrames * 1000 / GAME_FRAME_RATE;
+ delay(totalMilli);
+}
+
+bool Events::delay(uint32 time, bool interruptable) {
+ // Different handling for really short versus extended times
+ if (time < 10) {
+ // For really short periods, simply delay by the desired amount
+ pollEvents();
+ g_system->delayMillis(time);
+ bool result = !(interruptable && (kbHit() || _pressed || _vm->shouldQuit()));
+
+ clearEvents();
+ return result;
+ } else {
+ // For long periods go into a loop where we delay by 10ms at a time and then
+ // check for events. This ensures for longer delays that responsiveness is
+ // maintained
+ uint32 delayEnd = g_system->getMillis() + time;
+
+ while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) {
+ pollEventsAndWait();
+
+ if (interruptable && (kbHit() || _pressed)) {
+ clearEvents();
+ return false;
+ }
+ }
+
+ return !_vm->shouldQuit();
+ }
+}
+
+void Events::setButtonState() {
+ _firstPress = ((_mouseButtons & 1) && !_pressed) || ((_mouseButtons & 2) && !_rightPressed);
+
+ _released = _rightReleased = false;
+ if (_mouseButtons & LEFT_BUTTON)
+ _pressed = _oldButtons = true;
+
+ if ((_mouseButtons & LEFT_BUTTON) == 0 && _oldButtons) {
+ _pressed = _oldButtons = false;
+ _released = true;
+ }
+
+ if (_mouseButtons & RIGHT_BUTTON)
+ _rightPressed = _oldRightButton = true;
+
+ if ((_mouseButtons & RIGHT_BUTTON) == 0 && _oldRightButton) {
+ _rightPressed = _oldRightButton = false;
+ _rightReleased = true;
+ }
+}
+
+bool Events::checkInput() {
+ setButtonState();
+ return kbHit() || _pressed || _released || _rightPressed || _rightReleased;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h
new file mode 100644
index 0000000000..93a5e54f81
--- /dev/null
+++ b/engines/sherlock/events.h
@@ -0,0 +1,194 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_EVENTS_H
+#define SHERLOCK_EVENTS_H
+
+#include "common/scummsys.h"
+#include "common/events.h"
+#include "common/stack.h"
+#include "sherlock/image_file.h"
+
+namespace Sherlock {
+
+#define GAME_FRAME_RATE 60
+#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE)
+
+enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, EXIT_ZONES_START = 5, INVALID_CURSOR = -1 };
+
+class SherlockEngine;
+
+class Events {
+private:
+ SherlockEngine *_vm;
+ uint32 _frameCounter;
+ uint32 _priorFrameTime;
+ ImageFile *_cursorImages;
+ int _mouseButtons;
+
+ /**
+ * Check whether it's time to display the next screen frame
+ */
+ bool checkForNextFrameCounter();
+public:
+ CursorId _cursorId;
+ bool _pressed;
+ bool _released;
+ bool _rightPressed;
+ bool _rightReleased;
+ bool _oldButtons;
+ bool _oldRightButton;
+ bool _firstPress;
+ Common::Stack<Common::KeyState> _pendingKeys;
+ Common::Point _hotspotPos;
+public:
+ Events(SherlockEngine *vm);
+ ~Events();
+
+ /**
+ * Load a set of cursors from the specified file
+ */
+ void loadCursors(const Common::String &filename);
+
+ /**
+ * Set the cursor to show
+ */
+ void setCursor(CursorId cursorId);
+
+ /**
+ * Set the cursor to show from a passed frame
+ */
+ void setCursor(const Graphics::Surface &src, int hotspotX = 0, int hotspotY = 0);
+
+ /**
+ * Set both a standard cursor as well as an inventory item above it
+ */
+ void setCursor(CursorId cursorId, const Graphics::Surface &surface);
+
+ /**
+ * Animates the mouse cursor if the Wait cursor is showing
+ */
+ void animateCursorIfNeeded();
+
+ /**
+ * Show the mouse cursor
+ */
+ void showCursor();
+
+ /**
+ * Hide the mouse cursor
+ */
+ void hideCursor();
+
+ /**
+ * Returns the cursor
+ */
+ CursorId getCursor() const;
+
+ /**
+ * Returns true if the mouse cursor is visible
+ */
+ bool isCursorVisible() const;
+
+ /**
+ * Move the mouse
+ */
+ void moveMouse(const Common::Point &pt);
+
+ /**
+ * Check for any pending events
+ */
+ void pollEvents();
+
+ /**
+ * Poll for events and introduce a small delay, to allow the system to
+ * yield to other running programs
+ */
+ void pollEventsAndWait();
+
+ /**
+ * Move the mouse cursor
+ */
+ void warpMouse(const Common::Point &pt);
+
+ /**
+ * Get the current mouse position
+ */
+ Common::Point screenMousePos() const;
+
+ /**
+ * Get the current mouse position within the scene, adjusted by the scroll position
+ */
+ Common::Point mousePos() const;
+
+ /**
+ * Return the current game frame number
+ */
+ uint32 getFrameCounter() const { return _frameCounter; }
+
+ /**
+ * Returns true if there's a pending keyboard key
+ */
+ bool kbHit() const { return !_pendingKeys.empty(); }
+
+ /**
+ * Get a pending keypress
+ */
+ Common::KeyState getKey();
+
+ /**
+ * Clear any current keypress or mouse click
+ */
+ void clearEvents();
+
+ /**
+ * Clear any pending keyboard inputs
+ */
+ void clearKeyboard();
+
+ /**
+ * Delay for a given number of game frames, where each frame is 1/60th of a second
+ */
+ void wait(int numFrames);
+
+ /**
+ * Does a delay of the specified number of milliseconds
+ */
+ bool delay(uint32 time, bool interruptable = false);
+
+ /**
+ * Sets the pressed and released button flags on the raw button state previously set in pollEvents calls.
+ * @remarks The events manager has separate variables for the raw immediate and old button state
+ * versus the current buttons states for the frame. This method is expected to be called only once
+ * per game frame
+ */
+ void setButtonState();
+
+ /**
+ * Checks to see to see if a key or a mouse button is pressed.
+ */
+ bool checkInput();
+};
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_EVENTS_H */
diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp
new file mode 100644
index 0000000000..cbee944120
--- /dev/null
+++ b/engines/sherlock/fixed_text.cpp
@@ -0,0 +1,38 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/fixed_text.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+
+namespace Sherlock {
+
+FixedText *FixedText::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelFixedText(vm);
+ else
+ return new Tattoo::TattooFixedText(vm);
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/fixed_text.h b/engines/sherlock/fixed_text.h
new file mode 100644
index 0000000000..40444f4052
--- /dev/null
+++ b/engines/sherlock/fixed_text.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_FIXED_TEXT_H
+#define SHERLOCK_FIXED_TEXT_H
+
+#include "common/scummsys.h"
+#include "common/language.h"
+
+namespace Sherlock {
+
+#define FIXED(MSG) _vm->_fixedText->getText(kFixedText_##MSG)
+
+enum FixedTextActionId {
+ kFixedTextAction_Invalid = -1,
+ kFixedTextAction_Open = 0,
+ kFixedTextAction_Close,
+ kFixedTextAction_Move,
+ kFixedTextAction_Pick,
+ kFixedTextAction_Use
+};
+
+class SherlockEngine;
+
+class FixedText {
+protected:
+ SherlockEngine *_vm;
+
+ FixedText(SherlockEngine *vm) : _vm(vm) {}
+public:
+ static FixedText *init(SherlockEngine *vm);
+ virtual ~FixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId) = 0;
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) = 0;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp
new file mode 100644
index 0000000000..440e31915c
--- /dev/null
+++ b/engines/sherlock/fonts.cpp
@@ -0,0 +1,204 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "common/platform.h"
+#include "sherlock/fonts.h"
+#include "sherlock/image_file.h"
+#include "sherlock/surface.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+SherlockEngine *Fonts::_vm;
+ImageFile *Fonts::_font;
+int Fonts::_fontNumber;
+int Fonts::_fontHeight;
+int Fonts::_widestChar;
+uint16 Fonts::_charCount;
+byte Fonts::_yOffsets[255];
+
+void Fonts::setVm(SherlockEngine *vm) {
+ _vm = vm;
+ _font = nullptr;
+ _charCount = 0;
+}
+
+void Fonts::free() {
+ delete _font;
+}
+
+void Fonts::setFont(int fontNum) {
+ _fontNumber = fontNum;
+
+ // Discard previous font
+ delete _font;
+
+ Common::String fontFilename;
+
+ if (_vm->getPlatform() != Common::kPlatform3DO) {
+ // PC
+ // use FONT[number].VGS, which is a regular sherlock graphic file
+ fontFilename = Common::String::format("FONT%d.VGS", fontNum + 1);
+
+ // load font data
+ _font = new ImageFile(fontFilename);
+ } else {
+ // 3DO
+ switch (fontNum) {
+ case 0:
+ case 1:
+ fontFilename = "helvetica14.font";
+ break;
+ case 2:
+ fontFilename = "darts.font";
+ break;
+ default:
+ error("setFont(): unsupported 3DO font number");
+ }
+
+ // load font data
+ _font = new ImageFile3DO(fontFilename, kImageFile3DOType_Font);
+ }
+
+ _charCount = _font->size();
+
+ // Iterate through the frames to find the widest and tallest font characters
+ _fontHeight = _widestChar = 0;
+ for (uint idx = 0; idx < _charCount; ++idx) {
+ _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h);
+ _widestChar = MAX((uint16)_widestChar, (*_font)[idx]._frame.w);
+ }
+
+ // Initialize the Y offset table for the extended character set
+ for (int idx = 0; idx < 255; ++idx) {
+ _yOffsets[idx] = 0;
+
+ if (IS_ROSE_TATTOO) {
+ if ((idx >= 129 && idx < 135) || (idx >= 136 && idx < 143) || (idx >= 147 && idx < 155) ||
+ (idx >= 156 && idx < 165))
+ _yOffsets[idx] = 1;
+ else if ((idx >= 143 && idx < 146) || idx == 165)
+ _yOffsets[idx] = 2;
+ }
+ }
+}
+
+inline byte Fonts::translateChar(byte c) {
+ switch (c) {
+ case ' ':
+ return 0; // translate to first actual character
+ case 225:
+ // This was done in the German interpreter
+ // happens when talking to the kid in the 2nd room
+ return 135; // special handling for 0xE1
+ default:
+ if (c >= 0x80) { // German SH1 version did this
+ c--;
+ }
+ // Spanish SH1 did this (reverse engineered code)
+ //if ((c >= 0xA0) && (c <= 0xAD) || (c == 0x82)) {
+ // c--;
+ //}
+ assert(c > 32); // anything above space is allowed
+ return c - 33;
+ }
+}
+
+void Fonts::writeString(Surface *surface, const Common::String &str,
+ const Common::Point &pt, int overrideColor) {
+ Common::Point charPos = pt;
+
+ if (!_font)
+ return;
+
+ for (const char *curCharPtr = str.c_str(); *curCharPtr; ++curCharPtr) {
+ byte curChar = *curCharPtr;
+
+ if (curChar == ' ') {
+ charPos.x += 5; // hardcoded space
+ continue;
+ }
+ curChar = translateChar(curChar);
+
+ assert(curChar < _charCount);
+ ImageFrame &frame = (*_font)[curChar];
+ surface->transBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor);
+ charPos.x += frame._frame.w + 1;
+ }
+}
+
+int Fonts::stringWidth(const Common::String &str) {
+ int width = 0;
+
+ if (!_font)
+ return 0;
+
+ for (const char *c = str.c_str(); *c; ++c)
+ width += charWidth(*c);
+
+ return width;
+}
+
+int Fonts::stringHeight(const Common::String &str) {
+ int height = 0;
+
+ if (!_font)
+ return 0;
+
+ for (const char *c = str.c_str(); *c; ++c)
+ height = MAX(height, charHeight(*c));
+
+ return height;
+}
+
+int Fonts::charWidth(unsigned char c) {
+ byte curChar;
+
+ if (!_font)
+ return 0;
+
+ if (c == ' ') {
+ return 5; // hardcoded space
+ }
+ curChar = translateChar(c);
+
+ if (curChar < _charCount)
+ return (*_font)[curChar]._frame.w + 1;
+ return 0;
+}
+
+int Fonts::charHeight(unsigned char c) {
+ byte curChar;
+
+ if (!_font)
+ return 0;
+
+ // Space is supposed to be handled like the first actual character (which is decimal 33)
+ curChar = translateChar(c);
+
+ assert(curChar < _charCount);
+ const ImageFrame &img = (*_font)[curChar];
+ return img._height + img._offset.y + 1;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h
new file mode 100644
index 0000000000..a527cc73c0
--- /dev/null
+++ b/engines/sherlock/fonts.h
@@ -0,0 +1,105 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_FONTS_H
+#define SHERLOCK_FONTS_H
+
+#include "common/rect.h"
+#include "common/platform.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+class ImageFile;
+class Surface;
+
+class Fonts {
+private:
+ static ImageFile *_font;
+ static byte _yOffsets[255];
+protected:
+ static SherlockEngine *_vm;
+ static int _fontNumber;
+ static int _fontHeight;
+ static int _widestChar;
+ static uint16 _charCount;
+
+ static void writeString(Surface *surface, const Common::String &str,
+ const Common::Point &pt, int overrideColor = 0);
+
+ static inline byte translateChar(byte c);
+public:
+ /**
+ * Initialise the font manager
+ */
+ static void setVm(SherlockEngine *vm);
+
+ /**
+ * Frees the font manager
+ */
+ static void free();
+
+ /**
+ * Set the font to use for writing text on the screen
+ */
+ void setFont(int fontNum);
+
+ /**
+ * Returns the width of a string in pixels
+ */
+ int stringWidth(const Common::String &str);
+
+ /**
+ * Returns the height of a string in pixels (i.e. the tallest displayed character)
+ */
+ int stringHeight(const Common::String &str);
+
+ /**
+ * Returns the width of a character in pixels
+ */
+ int charWidth(unsigned char c);
+
+ /**
+ * Returns the width of a character in pixels
+ */
+ int charHeight(unsigned char c);
+
+ /**
+ * Return the font height
+ */
+ int fontHeight() const { return _fontHeight; }
+
+ /**
+ * Return the width of the widest character in the font
+ */
+ int widestChar() const { return _widestChar; }
+
+ /**
+ * Return the currently active font number
+ */
+ int fontNumber() const { return _fontNumber; }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/image_file.cpp b/engines/sherlock/image_file.cpp
new file mode 100644
index 0000000000..81087dae8b
--- /dev/null
+++ b/engines/sherlock/image_file.cpp
@@ -0,0 +1,1085 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/image_file.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+
+namespace Sherlock {
+
+SherlockEngine *ImageFile::_vm;
+
+void ImageFile::setVm(SherlockEngine *vm) {
+ _vm = vm;
+}
+
+ImageFile::ImageFile() {
+}
+
+ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) {
+ Common::SeekableReadStream *stream = _vm->_res->load(name);
+
+ Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
+ load(*stream, skipPal, animImages);
+
+ delete stream;
+}
+
+ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) {
+ Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
+ load(stream, skipPal, false);
+}
+
+ImageFile::~ImageFile() {
+ for (uint idx = 0; idx < size(); ++idx)
+ (*this)[idx]._frame.free();
+}
+
+void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) {
+ loadPalette(stream);
+
+ int streamSize = stream.size();
+ while (stream.pos() < streamSize) {
+ ImageFrame frame;
+ frame._width = stream.readUint16LE() + 1;
+ frame._height = stream.readUint16LE() + 1;
+ frame._paletteBase = stream.readByte();
+
+ if (animImages) {
+ // Animation cutscene image files use a 16-bit x offset
+ frame._offset.x = stream.readUint16LE();
+ frame._rleEncoded = (frame._offset.x & 0xff) == 1;
+ frame._offset.y = stream.readByte();
+ } else {
+ // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset
+ frame._rleEncoded = stream.readByte() == 1;
+ frame._offset.x = stream.readByte();
+ frame._offset.y = stream.readByte();
+ }
+
+ frame._rleEncoded = !skipPalette && frame._rleEncoded;
+
+ if (frame._paletteBase) {
+ // Nibble packed frame data
+ frame._size = (frame._width * frame._height) / 2;
+ } else if (frame._rleEncoded) {
+ // This size includes the header size, which we subtract
+ frame._size = stream.readUint16LE() - 11;
+ frame._rleMarker = stream.readByte();
+ } else {
+ // Uncompressed data
+ frame._size = frame._width * frame._height;
+ }
+
+ // Load data for frame and decompress it
+ byte *data = new byte[frame._size + 4];
+ stream.read(data, frame._size);
+ Common::fill(data + frame._size, data + frame._size + 4, 0);
+ frame.decompressFrame(data, IS_ROSE_TATTOO);
+ delete[] data;
+
+ push_back(frame);
+ }
+}
+
+void ImageFile::loadPalette(Common::SeekableReadStream &stream) {
+ // Check for palette
+ uint16 width = stream.readUint16LE() + 1;
+ uint16 height = stream.readUint16LE() + 1;
+ byte paletteBase = stream.readByte();
+ byte rleEncoded = stream.readByte();
+ byte offsetX = stream.readByte();
+ byte offsetY = stream.readByte();
+ uint32 palSignature = 0;
+
+ if ((width == 390) && (height == 2) && (!paletteBase) && (!rleEncoded) && (!offsetX) && (!offsetY)) {
+ // We check for these specific values
+ // We can't do "width * height", because at least the first German+Spanish menu bar is 60 x 13
+ // which is 780, which is the size of the palette. We obviously don't want to detect it as palette.
+
+ // As another security measure, we also check for the signature text
+ palSignature = stream.readUint32BE();
+ if (palSignature != MKTAG('V', 'G', 'A', ' ')) {
+ // signature mismatch, rewind
+ stream.seek(-12, SEEK_CUR);
+ return;
+ }
+ // Found palette, so read it in
+ stream.seek(8, SEEK_CUR); // Skip over the rest of the signature text "VGA palette"
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx)
+ _palette[idx] = VGA_COLOR_TRANS(stream.readByte());
+ } else {
+ // Not a palette, so rewind to start of frame data for normal frame processing
+ stream.seek(-8, SEEK_CUR);
+ }
+}
+
+void ImageFrame::decompressFrame(const byte *src, bool isRoseTattoo) {
+ _frame.create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
+ byte *dest = (byte *)_frame.getPixels();
+ Common::fill(dest, dest + _width * _height, 0xff);
+
+ if (_paletteBase) {
+ // Nibble-packed
+ for (uint idx = 0; idx < _size; ++idx, ++src) {
+ *dest++ = *src & 0xF;
+ *dest++ = (*src >> 4);
+ }
+ } else if (_rleEncoded && isRoseTattoo) {
+ // Rose Tattoo run length encoding doesn't use the RLE marker byte
+ for (int yp = 0; yp < _height; ++yp) {
+ int xSize = _width;
+ while (xSize > 0) {
+ // Skip a given number of pixels
+ byte skip = *src++;
+ dest += skip;
+ xSize -= skip;
+ if (!xSize)
+ break;
+
+ // Get a run length, and copy the following number of pixels
+ int rleCount = *src++;
+ xSize -= rleCount;
+ while (rleCount-- > 0)
+ *dest++ = *src++;
+ }
+ assert(xSize == 0);
+ }
+ } else if (_rleEncoded) {
+ // RLE encoded
+ int frameSize = _width * _height;
+ while (frameSize > 0) {
+ if (*src == _rleMarker) {
+ byte rleColor = src[1];
+ byte rleCount = src[2];
+ src += 3;
+ frameSize -= rleCount;
+ while (rleCount--)
+ *dest++ = rleColor;
+ } else {
+ *dest++ = *src++;
+ --frameSize;
+ }
+ }
+ assert(frameSize == 0);
+ } else {
+ // Uncompressed frame
+ Common::copy(src, src + _width * _height, dest);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+int ImageFrame::sDrawXSize(int scaleVal) const {
+ int width = _width;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --width;
+
+ int result = width * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawYSize(int scaleVal) const {
+ int height = _height;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --height;
+
+ int result = height * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawXOffset(int scaleVal) const {
+ int width = _offset.x;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --width;
+
+ int result = width * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+int ImageFrame::sDrawYOffset(int scaleVal) const {
+ int height = _offset.y;
+ int scale = scaleVal == 0 ? 1 : scaleVal;
+
+ if (scaleVal >= SCALE_THRESHOLD)
+ --height;
+
+ int result = height * SCALE_THRESHOLD / scale;
+ if (scaleVal >= SCALE_THRESHOLD)
+ ++result;
+
+ return result;
+}
+
+// *******************************************************
+
+/*----------------------------------------------------------------*/
+
+SherlockEngine *ImageFile3DO::_vm;
+
+void ImageFile3DO::setVm(SherlockEngine *vm) {
+ _vm = vm;
+}
+
+ImageFile3DO::ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType) {
+#if 0
+ Common::File *dataStream = new Common::File();
+
+ if (!dataStream->open(name)) {
+ error("unable to open %s\n", name.c_str());
+ }
+#endif
+ Common::SeekableReadStream *dataStream = _vm->_res->load(name);
+
+ switch(imageFile3DOType) {
+ case kImageFile3DOType_Animation:
+ loadAnimationFile(*dataStream);
+ break;
+ case kImageFile3DOType_Cel:
+ case kImageFile3DOType_CelAnimation:
+ load3DOCelFile(*dataStream);
+ break;
+ case kImageFile3DOType_RoomFormat:
+ load3DOCelRoomData(*dataStream);
+ break;
+ case kImageFile3DOType_Font:
+ loadFont(*dataStream);
+ break;
+ default:
+ error("unknown Imagefile-3DO-Type");
+ break;
+ }
+
+ delete dataStream;
+}
+
+ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData) {
+ if (!isRoomData) {
+ load(stream, isRoomData);
+ } else {
+ load3DOCelRoomData(stream);
+ }
+}
+
+ImageFile3DO::~ImageFile3DO() {
+ // already done in ImageFile destructor
+ //for (uint idx = 0; idx < size(); ++idx)
+ // (*this)[idx]._frame.free();
+}
+
+void ImageFile3DO::load(Common::SeekableReadStream &stream, bool isRoomData) {
+ uint32 headerId = 0;
+
+ if (isRoomData) {
+ load3DOCelRoomData(stream);
+ return;
+ }
+
+ headerId = stream.readUint32BE();
+ assert(!stream.eos());
+
+ // Seek back to the start
+ stream.seek(-4, SEEK_CUR);
+
+ // Identify type of file
+ switch (headerId) {
+ case MKTAG('C', 'C', 'B', ' '):
+ case MKTAG('A', 'N', 'I', 'M'):
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim)
+ load3DOCelFile(stream);
+ break;
+
+ default:
+ // Sherlock animation file (.3da files)
+ loadAnimationFile(stream);
+ break;
+ }
+}
+
+// 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us
+inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) {
+ byte red = (pixel3DO >> 10) & 0x1F;
+ byte green = (pixel3DO >> 5) & 0x1F;
+ byte blue = pixel3DO & 0x1F;
+
+ return ((red << 11) | (green << 6) | (blue));
+}
+
+void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream) {
+ uint32 streamLeft = stream.size() - stream.pos();
+ uint32 celDataSize = 0;
+
+ while (streamLeft > 0) {
+ ImageFrame frame;
+
+ // We expect a basic header of 8 bytes
+ if (streamLeft < 8)
+ error("load3DOAnimationFile: expected animation header, not enough bytes");
+
+ celDataSize = stream.readUint16BE();
+
+ frame._width = stream.readUint16BE() + 1; // 2 bytes BE width
+ frame._height = stream.readByte() + 1; // 1 byte BE height
+ frame._paletteBase = 0;
+
+ frame._rleEncoded = true; // always compressed
+ if (frame._width & 0x8000) {
+ frame._width &= 0x7FFF;
+ celDataSize += 0x10000;
+ }
+
+ frame._offset.x = stream.readUint16BE();
+ frame._offset.y = stream.readByte();
+ frame._size = 0;
+ // Got header
+ streamLeft -= 8;
+
+ // cel data follows
+ if (streamLeft < celDataSize)
+ error("load3DOAnimationFile: expected cel data, not enough bytes");
+
+ //
+ // Load data for frame and decompress it
+ byte *data = new byte[celDataSize];
+ stream.read(data, celDataSize);
+ streamLeft -= celDataSize;
+
+ // always 16 bits per pixel (RGB555)
+ decompress3DOCelFrame(frame, data, celDataSize, 16, NULL);
+
+ delete[] data;
+
+ push_back(frame);
+ }
+}
+
+static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = {
+ 0, 1, 2, 4, 6, 8, 16, 0
+};
+
+// Reads a 3DO .cel/.anim file
+void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) {
+ int32 streamSize = stream.size();
+ int32 chunkStartPos = 0;
+ uint32 chunkTag = 0;
+ uint32 chunkSize = 0;
+ byte *chunkDataPtr = NULL;
+
+ // ANIM chunk (animation header for animation files)
+ bool animFound = false;
+ uint32 animVersion = 0;
+ uint32 animType = 0;
+ uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header
+ // CCB chunk (cel control block)
+ bool ccbFound = false;
+ uint32 ccbVersion = 0;
+ uint32 ccbFlags = 0;
+ bool ccbFlags_compressed = false;
+ uint16 ccbPPMP0 = 0;
+ uint16 ccbPPMP1 = 0;
+ uint32 ccbPRE0 = 0;
+ uint16 ccbPRE0_height = 0;
+ byte ccbPRE0_bitsPerPixel = 0;
+ uint32 ccbPRE1 = 0;
+ uint16 ccbPRE1_width = 0;
+ uint32 ccbWidth = 0;
+ uint32 ccbHeight = 0;
+ // pixel lookup table
+ bool plutFound = false;
+ uint32 plutCount = 0;
+ ImageFile3DOPixelLookupTable plutRGBlookupTable;
+
+ memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable));
+
+ while (!stream.err() && (stream.pos() < streamSize)) {
+ chunkStartPos = stream.pos();
+ chunkTag = stream.readUint32BE();
+ chunkSize = stream.readUint32BE();
+
+ if (stream.eos() || stream.err())
+ break;
+
+ if (chunkSize < 8)
+ error("load3DOCelFile: Invalid chunk size");
+
+ uint32 dataSize = chunkSize - 8;
+
+ switch (chunkTag) {
+ case MKTAG('A', 'N', 'I', 'M'):
+ // animation header
+ assert(dataSize >= 24);
+
+ if (animFound)
+ error("load3DOCelFile: multiple ANIM chunks not supported");
+
+ animFound = true;
+ animVersion = stream.readUint32BE();
+ animType = stream.readUint32BE();
+ animFrameCount = stream.readUint32BE();
+ // UINT32 - framerate (0x2000 in walk.anim???)
+ // UINT32 - starting frame (0 for walk.anim)
+ // UINT32 - number of loops (0 for walk.anim)
+
+ if (animVersion != 0)
+ error("load3DOCelFile: Unsupported animation file version");
+ if (animType != 1)
+ error("load3DOCelFile: Only single CCB animation files are supported");
+ break;
+
+ case MKTAG('C', 'C', 'B', ' '):
+ // CEL control block
+ assert(dataSize >= 72);
+
+ if (ccbFound)
+ error("load3DOCelFile: multiple CCB chunks not supported");
+
+ ccbFound = true;
+ ccbVersion = stream.readUint32BE();
+ ccbFlags = stream.readUint32BE();
+ stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
+ stream.skip(8 * 4); // skip over 8 offset fields
+ ccbPPMP0 = stream.readUint16BE();
+ ccbPPMP1 = stream.readUint16BE();
+ ccbPRE0 = stream.readUint32BE();
+ ccbPRE1 = stream.readUint32BE();
+ ccbWidth = stream.readUint32BE();
+ ccbHeight = stream.readUint32BE();
+
+ if (ccbVersion != 0)
+ error("load3DOCelFile: Unsupported CCB version");
+
+ if (ccbFlags & 0x200) // bit 9
+ ccbFlags_compressed = true;
+
+ // bit 5 of ccbFlags defines how RGB-black (0, 0, 0) will get treated
+ // = false -> RGB-black is treated as transparent
+ // = true -> RGB-black is treated as actual black
+ // atm we are always treating it as transparent
+ // it seems this bit is not set for any data of Sherlock Holmes
+
+ // PRE0 first 3 bits define how many bits per encoded pixel are used
+ ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
+ if (!ccbPRE0_bitsPerPixel)
+ error("load3DOCelFile: Invalid CCB PRE0 bits per pixel");
+
+ ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
+ ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
+ assert(ccbPRE0_height == ccbHeight);
+ assert(ccbPRE1_width == ccbWidth);
+ break;
+
+ case MKTAG('P', 'L', 'U', 'T'):
+ // pixel lookup table
+ // optional, not required for at least 16-bit pixel data
+ assert(dataSize >= 6);
+
+ if (!ccbFound)
+ error("load3DOCelFile: PLUT chunk found without CCB chunk");
+ if (plutFound)
+ error("load3DOCelFile: multiple PLUT chunks currently not supported");
+
+ plutFound = true;
+ plutCount = stream.readUint32BE();
+ // table follows, each entry is 16bit RGB555
+ assert(dataSize >= 4 + (plutCount * 2)); // security check
+ assert(plutCount <= 256); // security check
+
+ assert(plutCount <= 32); // PLUT should never contain more than 32 entries
+
+ for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
+ plutRGBlookupTable.pixelColor[plutColorNr] = stream.readUint16BE();
+ }
+
+ if (ccbPRE0_bitsPerPixel == 8) {
+ // In case we are getting 8-bits per pixel, we calculate the shades accordingly
+ // I'm not 100% sure if the calculation is correct. It's difficult to find information
+ // on this topic.
+ // The map uses this type of cel
+ assert(plutCount == 32); // And we expect 32 entries inside PLUT chunk
+
+ uint16 plutColorRGB = 0;
+ for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) {
+ plutColorRGB = plutRGBlookupTable.pixelColor[plutColorNr];
+
+ // Extract RGB values
+ byte plutColorRed = (plutColorRGB >> 10) & 0x1F;
+ byte plutColorGreen = (plutColorRGB >> 5) & 0x1F;
+ byte plutColorBlue = plutColorRGB & 0x1F;
+
+ byte shadeMultiplier = 2;
+ for (uint32 plutShadeNr = 1; plutShadeNr < 8; plutShadeNr++) {
+ uint16 shadedColorRGB;
+ byte shadedColorRed = (plutColorRed * shadeMultiplier) >> 3;
+ byte shadedColorGreen = (plutColorGreen * shadeMultiplier) >> 3;
+ byte shadedColorBlue = (plutColorBlue * shadeMultiplier) >> 3;
+
+ shadedColorRed = CLIP<byte>(shadedColorRed, 0, 0x1F);
+ shadedColorGreen = CLIP<byte>(shadedColorGreen, 0, 0x1F);
+ shadedColorBlue = CLIP<byte>(shadedColorBlue, 0, 0x1F);
+ shadedColorRGB = (shadedColorRed << 10) | (shadedColorGreen << 5) | shadedColorBlue;
+
+ plutRGBlookupTable.pixelColor[plutColorNr + (plutShadeNr << 5)] = shadedColorRGB;
+ shadeMultiplier++;
+ }
+ }
+ }
+ break;
+
+ case MKTAG('X', 'T', 'R', 'A'):
+ // Unknown contents, occurs right before PDAT
+ break;
+
+ case MKTAG('P', 'D', 'A', 'T'): {
+ // pixel data for one frame
+ // may be compressed or uncompressed pixels
+
+ if (ccbPRE0_bitsPerPixel != 16) {
+ // We require a pixel lookup table in case bits-per-pixel is lower than 16
+ if (!plutFound)
+ error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found");
+ } else {
+ // But we don't like it in case bits-per-pixel is 16 and we find one
+ if (plutFound)
+ error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well");
+ }
+ // read data into memory
+ chunkDataPtr = new byte[dataSize];
+
+ stream.read(chunkDataPtr, dataSize);
+
+ // Set up frame
+ ImageFrame imageFrame;
+
+ imageFrame._width = ccbWidth;
+ imageFrame._height = ccbHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = 0;
+ imageFrame._offset.y = 0;
+ imageFrame._rleEncoded = ccbFlags_compressed;
+ imageFrame._size = 0;
+
+ // Decompress/copy this frame
+ if (!plutFound) {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, NULL);
+ } else {
+ decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, &plutRGBlookupTable);
+ }
+
+ delete[] chunkDataPtr;
+
+ push_back(imageFrame);
+ break;
+ }
+
+ case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel
+ // unknown contents
+ break;
+
+ default:
+ error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag));
+ }
+
+ // Seek to end of chunk
+ stream.seek(chunkStartPos + chunkSize);
+ }
+
+ // Warning below being used to silence unused variable warnings for now
+ warning("TODO: Remove %d %d %d", animFrameCount, ccbPPMP0, ccbPPMP1);
+}
+
+// Reads 3DO .cel data (room file format)
+void ImageFile3DO::load3DOCelRoomData(Common::SeekableReadStream &stream) {
+ uint32 streamLeft = stream.size() - stream.pos();
+ uint16 roomDataHeader_size = 0;
+ byte roomDataHeader_offsetX = 0;
+ byte roomDataHeader_offsetY = 0;
+
+ // CCB chunk (cel control block)
+ uint32 ccbFlags = 0;
+ bool ccbFlags_compressed = false;
+ uint16 ccbPPMP0 = 0;
+ uint16 ccbPPMP1 = 0;
+ uint32 ccbPRE0 = 0;
+ uint16 ccbPRE0_height = 0;
+ byte ccbPRE0_bitsPerPixel = 0;
+ uint32 ccbPRE1 = 0;
+ uint16 ccbPRE1_width = 0;
+ uint32 ccbWidth = 0;
+ uint32 ccbHeight = 0;
+ // cel data
+ uint32 celDataSize = 0;
+
+ while (streamLeft > 0) {
+ // We expect at least 8 bytes basic header
+ if (streamLeft < 8)
+ error("load3DOCelRoomData: expected room data header, not enough bytes");
+
+ // 3DO sherlock holmes room data header
+ stream.skip(4); // Possibly UINT16 width, UINT16 height?!?!
+ roomDataHeader_size = stream.readUint16BE();
+ roomDataHeader_offsetX = stream.readByte();
+ roomDataHeader_offsetY = stream.readByte();
+ streamLeft -= 8;
+
+ // We expect the header size specified in the basic header to be at least a raw CCB
+ if (roomDataHeader_size < 68)
+ error("load3DOCelRoomData: header size is too small");
+ // Check, that enough bytes for CCB are available
+ if (streamLeft < 68)
+ error("load3DOCelRoomData: expected raw cel control block, not enough bytes");
+
+ // 3DO raw cel control block
+ ccbFlags = stream.readUint32BE();
+ stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware
+ stream.skip(8 * 4); // skip over 8 offset fields
+ ccbPPMP0 = stream.readUint16BE();
+ ccbPPMP1 = stream.readUint16BE();
+ ccbPRE0 = stream.readUint32BE();
+ ccbPRE1 = stream.readUint32BE();
+ ccbWidth = stream.readUint32BE();
+ ccbHeight = stream.readUint32BE();
+
+ if (ccbFlags & 0x200) // bit 9
+ ccbFlags_compressed = true;
+
+ // PRE0 first 3 bits define how many bits per encoded pixel are used
+ ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07];
+ if (!ccbPRE0_bitsPerPixel)
+ error("load3DOCelRoomData: Invalid CCB PRE0 bits per pixel");
+
+ ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1;
+ ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1;
+ assert(ccbPRE0_height == ccbHeight);
+ assert(ccbPRE1_width == ccbWidth);
+
+ if (ccbPRE0_bitsPerPixel != 16) {
+ // We currently support 16-bits per pixel in here
+ error("load3DOCelRoomData: bits per pixel < 16?!?!?");
+ }
+ // Got the raw CCB
+ streamLeft -= 68;
+
+ // cel data follows
+ // size field does not include the 8 byte header
+ celDataSize = roomDataHeader_size - 68;
+
+ if (streamLeft < celDataSize)
+ error("load3DOCelRoomData: expected cel data, not enough bytes");
+
+ // read data into memory
+ byte *celDataPtr = new byte[celDataSize];
+
+ stream.read(celDataPtr, celDataSize);
+ streamLeft -= celDataSize;
+
+ // Set up frame
+ {
+ ImageFrame imageFrame;
+
+ imageFrame._width = ccbWidth;
+ imageFrame._height = ccbHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = roomDataHeader_offsetX;
+ imageFrame._offset.y = roomDataHeader_offsetY;
+ imageFrame._rleEncoded = ccbFlags_compressed;
+ imageFrame._size = 0;
+
+ // Decompress/copy this frame
+ decompress3DOCelFrame(imageFrame, celDataPtr, celDataSize, ccbPRE0_bitsPerPixel, NULL);
+
+ delete[] celDataPtr;
+
+ push_back(imageFrame);
+ }
+ }
+
+ // Suppress compiler warning
+ warning("ccbPPMP0 = %d, ccbPPMP1 = %d", ccbPPMP0, ccbPPMP1);
+}
+
+static uint16 imagefile3DO_cel_bitsMask[17] = {
+ 0,
+ 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+};
+
+// gets [bitCount] bits from dataPtr, going from MSB to LSB
+inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) {
+ byte resultBitsLeft = bitCount;
+ uint16 result = 0;
+ byte currentByte = *dataPtr;
+
+ // Get bits of current byte
+ while (resultBitsLeft) {
+ if (resultBitsLeft < dataBitsLeft) {
+ // we need less than we have left
+ result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft];
+ dataBitsLeft -= resultBitsLeft;
+ resultBitsLeft = 0;
+
+ } else {
+ // we need as much as we have left or more
+ resultBitsLeft -= dataBitsLeft;
+ result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft;
+
+ // Go to next byte
+ dataPtr++;
+ dataBitsLeft = 8;
+ if (resultBitsLeft) {
+ currentByte = *dataPtr;
+ }
+ }
+ }
+ return result;
+}
+
+// decompress/copy 3DO cel data
+void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) {
+ frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ uint16 *dest = (uint16 *)frame._frame.getPixels();
+ Common::fill(dest, dest + frame._width * frame._height, 0);
+
+ int frameHeightLeft = frame._height;
+ int frameWidthLeft = frame._width;
+ uint16 pixelCount = 0;
+ uint16 pixel = 0;
+
+ const byte *srcLineStart = dataPtr;
+ const byte *srcLineData = dataPtr;
+ byte srcLineDataBitsLeft = 0;
+ uint16 lineDWordSize = 0;
+ uint16 lineByteSize = 0;
+
+ if (bitsPerPixel == 16) {
+ // Must not use pixel lookup table on 16-bits-per-pixel data
+ assert(!pixelLookupTable);
+ }
+
+ if (frame._rleEncoded) {
+ // compressed
+ byte compressionType = 0;
+ byte compressionPixels = 0;
+
+ while (frameHeightLeft > 0) {
+ frameWidthLeft = frame._width;
+
+ if (bitsPerPixel >= 8) {
+ lineDWordSize = READ_BE_UINT16(srcLineStart);
+ srcLineData = srcLineStart + 2;
+ } else {
+ lineDWordSize = *srcLineStart;
+ srcLineData = srcLineStart + 1;
+ }
+ srcLineDataBitsLeft = 8;
+
+ lineDWordSize += 2;
+ lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line
+
+ // debug
+ //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize);
+
+ while (frameWidthLeft > 0) {
+ // get 2 bits -> compressionType
+ // get 6 bits -> pixel count (0 = 1 pixel)
+ compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft);
+ // 6 bits == length (0 = 1 pixel)
+ compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1;
+
+ if (!compressionType) // end of line
+ break;
+
+ switch(compressionType) {
+ case 1: // simple copy
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
+ }
+ break;
+ case 2: // transparent
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = 0;
+ }
+ break;
+ case 3: // duplicate pixels
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ pixel = convertPixel(pixel);
+ for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) {
+ *dest++ = pixel;
+ }
+ break;
+ default:
+ break;
+ }
+ frameWidthLeft -= compressionPixels;
+ }
+
+ assert(frameWidthLeft >= 0);
+
+ if (frameWidthLeft > 0) {
+ // still pixels left? skip them
+ dest += frameWidthLeft;
+ }
+
+ frameHeightLeft--;
+
+ // Seek to next line start
+ srcLineStart += lineByteSize;
+ }
+ } else {
+ // uncompressed
+ srcLineDataBitsLeft = 8;
+ lineDWordSize = ((frame._width * bitsPerPixel) + 31) >> 5;
+ lineByteSize = lineDWordSize * 4;
+ uint32 totalExpectedSize = lineByteSize * frame._height;
+
+ assert(totalExpectedSize <= dataSize); // security check
+
+ while (frameHeightLeft > 0) {
+ srcLineData = srcLineStart;
+ frameWidthLeft = frame._width;
+
+ while (frameWidthLeft > 0) {
+ pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft);
+ if (pixelLookupTable) {
+ pixel = pixelLookupTable->pixelColor[pixel];
+ }
+ *dest++ = convertPixel(pixel);
+
+ frameWidthLeft--;
+ }
+ frameHeightLeft--;
+
+ // Seek to next line start
+ srcLineStart += lineByteSize;
+ }
+ }
+}
+
+// Reads Sherlock Holmes 3DO font file
+void ImageFile3DO::loadFont(Common::SeekableReadStream &stream) {
+ uint32 streamSize = stream.size();
+ uint32 header_offsetWidthTable = 0;
+ uint32 header_offsetBitsTable = 0;
+ uint32 header_fontHeight = 0;
+ uint32 header_bytesPerLine = 0;
+ uint32 header_maxChar = 0;
+ uint32 header_charCount = 0;
+
+ byte *widthTablePtr = NULL;
+ uint32 bitsTableSize = 0;
+ byte *bitsTablePtr = NULL;
+
+ stream.skip(2); // Unknown bytes
+ stream.skip(2); // Unknown bytes (0x000E)
+ header_offsetWidthTable = stream.readUint32BE();
+ header_offsetBitsTable = stream.readUint32BE();
+ stream.skip(4); // Unknown bytes (0x00000004)
+ header_fontHeight = stream.readUint32BE();
+ header_bytesPerLine = stream.readUint32BE();
+ header_maxChar = stream.readUint32BE();
+
+ assert(header_maxChar <= 255);
+ header_charCount = header_maxChar + 1;
+
+ // Allocate memory for width table
+ widthTablePtr = new byte[header_charCount];
+
+ stream.seek(header_offsetWidthTable);
+ stream.read(widthTablePtr, header_charCount);
+
+ // Allocate memory for the bits
+ assert(header_offsetBitsTable < streamSize); // Security check
+ bitsTableSize = streamSize - header_offsetBitsTable;
+ bitsTablePtr = new byte[bitsTableSize];
+ stream.read(bitsTablePtr, bitsTableSize);
+
+ // Now extract all characters
+ uint16 curChar = 0;
+ const byte *curBitsLinePtr = bitsTablePtr;
+ const byte *curBitsPtr = NULL;
+ byte curBitsLeft = 0;
+ uint32 curCharHeightLeft = 0;
+ uint32 curCharWidthLeft = 0;
+ byte curBits = 0;
+ byte curBitsReversed = 0;
+ byte curPosX = 0;
+
+ assert(bitsTableSize >= (header_maxChar * header_fontHeight * header_bytesPerLine)); // Security
+
+ // first frame needs to be "!" (33 decimal)
+ // our font code is subtracting 33 from the actual character code
+ curBitsLinePtr += (33 * (header_fontHeight * header_bytesPerLine));
+
+ for (curChar = 33; curChar < header_charCount; curChar++) {
+ // create frame
+ {
+ ImageFrame imageFrame;
+
+ imageFrame._width = widthTablePtr[curChar];
+ imageFrame._height = header_fontHeight;
+ imageFrame._paletteBase = 0;
+ imageFrame._offset.x = 0;
+ imageFrame._offset.y = 0;
+ imageFrame._rleEncoded = false;
+ imageFrame._size = 0;
+
+ // Extract pixels
+ imageFrame._frame.create(imageFrame._width, imageFrame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ uint16 *dest = (uint16 *)imageFrame._frame.getPixels();
+ Common::fill(dest, dest + imageFrame._width * imageFrame._height, 0);
+
+ curCharHeightLeft = header_fontHeight;
+ while (curCharHeightLeft) {
+ curCharWidthLeft = widthTablePtr[curChar];
+ curBitsPtr = curBitsLinePtr;
+ curBitsLeft = 8;
+ curPosX = 0;
+
+ while (curCharWidthLeft) {
+ if (!(curPosX & 1)) {
+ curBits = *curBitsPtr >> 4;
+ } else {
+ curBits = *curBitsPtr & 0x0F;
+ curBitsPtr++;
+ }
+ // doing this properly is complicated
+ // the 3DO has built-in anti-aliasing
+ // this here at least results in somewhat readable text
+ // TODO: make it better
+ if (curBits) {
+ curBitsReversed = (curBits >> 3) | ((curBits & 0x04) >> 1) | ((curBits & 0x02) << 1) | ((curBits & 0x01) << 3);
+ curBits = 20 - curBits;
+ }
+
+ byte curIntensity = curBits;
+ *dest = (curIntensity << 11) | (curIntensity << 6) | curIntensity;
+ dest++;
+
+ curCharWidthLeft--;
+ curPosX++;
+ }
+
+ curCharHeightLeft--;
+ curBitsLinePtr += header_bytesPerLine;
+ }
+
+ push_back(imageFrame);
+ }
+ }
+
+ // Warning below being used to silence unused variable warnings for now
+ warning("TODO: Remove %d %d", curBitsLeft, curBitsReversed);
+
+ delete[] bitsTablePtr;
+ delete[] widthTablePtr;
+}
+
+/*----------------------------------------------------------------*/
+
+StreamingImageFile::StreamingImageFile() {
+ _frameNumber = 0;
+ _stream = nullptr;
+ _flags = 0;
+ _scaleVal = 0;
+ _zPlacement = 0;
+ _compressed = false;
+}
+
+StreamingImageFile::~StreamingImageFile() {
+ close();
+}
+
+void StreamingImageFile::load(Common::SeekableReadStream *stream, bool compressed) {
+ _stream = stream;
+ _compressed = compressed;
+ _frameNumber = -1;
+}
+
+void StreamingImageFile::close() {
+ delete _stream;
+ _stream = nullptr;
+ _frameNumber = -1;
+}
+
+void StreamingImageFile::getNextFrame() {
+ // Don't proceed if we're already at the end of the stream
+ if (_stream->pos() >= _stream->size())
+ return;
+
+ // Increment frame number
+ ++_frameNumber;
+
+ // If necessary, decompress the next frame
+ Common::SeekableReadStream *frameStream = _stream;
+ if (_compressed) {
+ uint32 inSize = _stream->readUint32LE();
+ Resources::decompressLZ(*_stream, _buffer, STREAMING_BUFFER_SIZE, inSize);
+ frameStream = new Common::MemoryReadStream(_buffer, 11, DisposeAfterUse::NO);
+ }
+
+ // Load the data for the frame
+ _imageFrame._width = frameStream->readUint16LE() + 1;
+ _imageFrame._height = frameStream->readUint16LE() + 1;
+ _imageFrame._paletteBase = frameStream->readByte();
+ _imageFrame._rleEncoded = frameStream->readByte() == 1;
+ _imageFrame._offset.x = frameStream->readByte();
+ _imageFrame._offset.y = frameStream->readByte();
+ _imageFrame._size = frameStream->readUint16LE() - 11;
+ _imageFrame._rleMarker = frameStream->readByte();
+
+ // Decode the frame
+ if (_compressed) {
+ delete frameStream;
+ _imageFrame.decompressFrame(_buffer + 11, true);
+ } else {
+ byte *data = new byte[_imageFrame._size];
+ _stream->read(data, _imageFrame._size);
+ _imageFrame.decompressFrame(_buffer + 11, true);
+ delete[] data;
+ }
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/image_file.h b/engines/sherlock/image_file.h
new file mode 100644
index 0000000000..3ac0cf4b5f
--- /dev/null
+++ b/engines/sherlock/image_file.h
@@ -0,0 +1,207 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_IMAGE_FILE_H
+#define SHERLOCK_IMAGE_FILE_H
+
+#include "common/array.h"
+#include "common/file.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+struct ImageFrame {
+ uint32 _size;
+ uint16 _width, _height;
+ int _paletteBase;
+ bool _rleEncoded;
+ Common::Point _offset;
+ byte _rleMarker;
+ Graphics::Surface _frame;
+
+ /**
+ * Decompress a single frame for the sprite
+ */
+ void decompressFrame(const byte *src, bool isRoseTattoo);
+
+ /**
+ * Return the frame width adjusted by a specified scale amount
+ */
+ int sDrawXSize(int scaleVal) const;
+
+ /**
+ * Return the frame height adjusted by a specified scale amount
+ */
+ int sDrawYSize(int scaleVal) const;
+
+ /**
+ * Return the frame offset x adjusted by a specified scale amount
+ */
+ int sDrawXOffset(int scaleVal) const;
+
+ /**
+ * Return the frame offset y adjusted by a specified scale amount
+ */
+ int sDrawYOffset(int scaleVal) const;
+};
+
+class ImageFile : public Common::Array<ImageFrame> {
+private:
+ static SherlockEngine *_vm;
+
+ /**
+ * Load the data of the sprite
+ */
+ void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages);
+
+ /**
+ * Gets the palette at the start of the sprite file
+ */
+ void loadPalette(Common::SeekableReadStream &stream);
+public:
+ byte _palette[256 * 3];
+public:
+ ImageFile();
+ ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false);
+ ImageFile(Common::SeekableReadStream &stream, bool skipPal = false);
+ ~ImageFile();
+ static void setVm(SherlockEngine *vm);
+};
+
+enum ImageFile3DOType {
+ kImageFile3DOType_Animation = 0,
+ kImageFile3DOType_Cel = 1,
+ kImageFile3DOType_CelAnimation = 2,
+ kImageFile3DOType_RoomFormat = 3,
+ kImageFile3DOType_Font = 4
+};
+
+struct ImageFile3DOPixelLookupTable {
+ uint16 pixelColor[256];
+};
+
+class ImageFile3DO : public ImageFile { // Common::Array<ImageFrame> {
+private:
+ static SherlockEngine *_vm;
+
+ /**
+ * Load the data of the sprite
+ */
+ void load(Common::SeekableReadStream &stream, bool isRoomData);
+
+ /**
+ * convert pixel RGB values from RGB555 to RGB565
+ */
+ inline uint16 convertPixel(uint16 pixel3DO);
+
+ /**
+ * Load 3DO cel file
+ */
+ void load3DOCelFile(Common::SeekableReadStream &stream);
+
+ /**
+ * Load 3DO cel data (room file format)
+ */
+ void load3DOCelRoomData(Common::SeekableReadStream &stream);
+
+ inline uint16 celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft);
+
+ /**
+ * Decompress a single frame of a 3DO cel file
+ */
+ void decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable);
+
+ /**
+ * Load animation graphics file
+ */
+ void loadAnimationFile(Common::SeekableReadStream &stream);
+
+ /**
+ * Load Sherlock Holmes 3DO font file
+ */
+ void loadFont(Common::SeekableReadStream &stream);
+
+public:
+ ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType);
+ ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData = false);
+ ~ImageFile3DO();
+ static void setVm(SherlockEngine *vm);
+};
+
+#define STREAMING_BUFFER_SIZE 65536
+
+class StreamingImageFile {
+private:
+ int _frameNumber;
+ Common::SeekableReadStream *_stream;
+ bool _compressed;
+ byte _buffer[STREAMING_BUFFER_SIZE];
+public:
+ ImageFrame _imageFrame;
+
+ Common::Point _position; // Animation position
+ Common::Rect _oldBounds; // Bounds of previous frame
+ Common::Rect _removeBounds; // Remove area for just drawn frame
+
+ int _flags; // Flags
+ int _scaleVal; // Specifies the scale amount
+ int _zPlacement; // Used by doBgAnim for determining Z order
+public:
+ StreamingImageFile();
+ ~StreamingImageFile();
+
+ /**
+ * Initialize reading of the specified stream
+ */
+ void load(Common::SeekableReadStream *stream, bool compressed);
+
+ /**
+ * Close the streamining image file
+ */
+ void close();
+
+ /**
+ * Get the next frame of the file
+ */
+ void getNextFrame();
+
+ /**
+ * Returns whether there are any remaining frames or not
+ */
+ bool active() const { return _stream != nullptr && _stream->pos() < _stream->size(); }
+
+ /**
+ * Return the current frame number
+ */
+ int frameNumber() const { return _frameNumber; }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp
new file mode 100644
index 0000000000..d0982542f2
--- /dev/null
+++ b/engines/sherlock/inventory.cpp
@@ -0,0 +1,246 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/inventory.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_inventory.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo_inventory.h"
+
+namespace Sherlock {
+
+InventoryItem::InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine) :
+ _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description),
+ _examine(examine), _lookFlag(0) {
+}
+
+InventoryItem::InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine, const Common::String &verbName) :
+ _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description),
+ _examine(examine), _lookFlag(0) {
+ _verb._verb = verbName;
+}
+
+void InventoryItem::synchronize(Serializer &s) {
+ s.syncAsSint16LE(_requiredFlag);
+ s.syncAsSint16LE(_lookFlag);
+ s.syncString(_name);
+ s.syncString(_description);
+ s.syncString(_examine);
+ _verb.synchronize(s);
+}
+
+/*----------------------------------------------------------------*/
+
+Inventory *Inventory::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelInventory(vm);
+ else
+ return new Tattoo::TattooInventory(vm);
+}
+
+Inventory::Inventory(SherlockEngine *vm) : Common::Array<InventoryItem>(), _vm(vm) {
+ _invGraphicsLoaded = false;
+ _invIndex = 0;
+ _holdings = 0;
+ _invMode = INVMODE_EXIT;
+}
+
+Inventory::~Inventory() {
+ freeGraphics();
+}
+
+void Inventory::freeInv() {
+ freeGraphics();
+
+ _names.clear();
+ _invGraphicsLoaded = false;
+}
+
+void Inventory::freeGraphics() {
+ int count = _invShapes.size();
+ for (int idx = 0; idx < count; ++idx)
+ delete _invShapes[idx];
+ _invShapes.clear();
+ _invShapes.resize(count);
+
+ _invGraphicsLoaded = false;
+}
+
+void Inventory::loadGraphics() {
+ if (_invGraphicsLoaded)
+ return;
+
+ for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < (int)_invShapes.size(); ++idx) {
+ // Get the name of the item to be displayed, figure out its accompanying
+ // .VGS file with its picture, and then load it
+ int invNum = findInv((*this)[idx]._name);
+ Common::String filename = Common::String::format("item%02d.vgs", invNum + 1);
+
+ if (!IS_3DO) {
+ // PC
+ _invShapes[idx - _invIndex] = new ImageFile(filename);
+ } else {
+ _invShapes[idx - _invIndex] = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat);
+ }
+ }
+
+ _invGraphicsLoaded = true;
+}
+
+int Inventory::findInv(const Common::String &name) {
+ for (int idx = 0; idx < (int)_names.size(); ++idx) {
+ if (name.equalsIgnoreCase(_names[idx]))
+ return idx;
+ }
+
+ // Couldn't find the desired item
+ error("Couldn't find inventory item - %s", name.c_str());
+}
+
+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 (name.equalsIgnoreCase(o._name) && o._type != INVALID) {
+ putItemInInventory(o);
+ ++matches;
+ }
+ }
+
+ return matches;
+}
+
+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 < USE_COUNT; ++useNum) {
+ if (obj._use[useNum]._target.equalsIgnoreCase("*PICKUP*")) {
+ pickupFound = true;
+
+ for (int namesNum = 0; namesNum < NAMES_COUNT; ++namesNum) {
+ for (uint bgNum = 0; bgNum < scene._bgShapes.size(); ++bgNum) {
+ Object &bgObj = scene._bgShapes[bgNum];
+ if (obj._use[useNum]._names[namesNum].equalsIgnoreCase(bgObj._name)) {
+ 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;
+}
+
+void Inventory::copyToInventory(Object &obj) {
+ InventoryItem invItem;
+ invItem._name = obj._name;
+ invItem._description = obj._description;
+ invItem._examine = obj._examine;
+ invItem._lookFlag = obj._lookFlag;
+ invItem._requiredFlag = obj._requiredFlag[0];
+
+ insert_at(_holdings, invItem);
+ ++_holdings;
+}
+
+int Inventory::deleteItemFromInventory(const Common::String &name) {
+ int invNum = -1;
+
+ for (int idx = 0; idx < (int)size() && invNum == -1; ++idx) {
+ if (name.equalsIgnoreCase((*this)[idx]._name))
+ invNum = idx;
+ }
+
+ if (invNum == -1)
+ // Item not present
+ return 0;
+
+ // Item found, so delete it
+ remove_at(invNum);
+ --_holdings;
+
+ return 1;
+}
+
+void Inventory::synchronize(Serializer &s) {
+ s.syncAsSint16LE(_holdings);
+
+ uint count = size();
+ s.syncAsUint16LE(count);
+ if (s.isLoading()) {
+ resize(count);
+
+ // Reset inventory back to start
+ _invIndex = 0;
+ }
+
+ for (uint idx = 0; idx < size(); ++idx) {
+ (*this)[idx].synchronize(s);
+
+ }
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h
new file mode 100644
index 0000000000..057e923e5d
--- /dev/null
+++ b/engines/sherlock/inventory.h
@@ -0,0 +1,150 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_INVENTORY_H
+#define SHERLOCK_INVENTORY_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/str-array.h"
+#include "sherlock/objects.h"
+#include "sherlock/resources.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+enum InvMode {
+ INVMODE_EXIT = 0,
+ INVMODE_LOOK = 1,
+ INVMODE_USE = 2,
+ INVMODE_GIVE = 3,
+ INVMODE_FIRST = 4,
+ INVMODE_PREVIOUS = 5,
+ INVMODE_NEXT = 6,
+ INVMODE_LAST = 7,
+ INVMODE_INVALID = 8,
+ INVMODE_USE55 = 255
+};
+
+enum InvNewMode {
+ PLAIN_INVENTORY = 0, LOOK_INVENTORY_MODE = 1, USE_INVENTORY_MODE = 2,
+ GIVE_INVENTORY_MODE = 3, INVENTORY_DONT_DISPLAY = 128
+};
+
+enum InvSlamMode { SLAM_DONT_DISPLAY, SLAM_DISPLAY = 1, SLAM_SECONDARY_BUFFER };
+
+
+struct InventoryItem {
+ int _requiredFlag;
+ Common::String _name;
+ Common::String _description;
+ Common::String _examine;
+ int _lookFlag;
+
+ // Rose Tattoo fields
+ int _requiredFlag1;
+ UseType _verb;
+
+ InventoryItem() : _requiredFlag(0), _lookFlag(0), _requiredFlag1(0) {}
+ InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine);
+ InventoryItem(int requiredFlag, const Common::String &name,
+ const Common::String &description, const Common::String &examine, const Common::String &verbName);
+
+ /**
+ * Synchronize the data for an inventory item
+ */
+ void synchronize(Serializer &s);
+};
+
+class Inventory : public Common::Array<InventoryItem> {
+protected:
+ SherlockEngine *_vm;
+ Common::StringArray _names;
+
+ /**
+ * Copy the passed object into the inventory
+ */
+ void copyToInventory(Object &obj);
+public:
+ Common::Array<ImageFile *> _invShapes;
+ bool _invGraphicsLoaded;
+ InvMode _invMode;
+ int _invIndex;
+ int _holdings; // Used to hold number of visible items in active inventory.
+ // Since Inventory array also contains some special hidden items
+ /**
+ * Free any loaded inventory graphics
+ */
+ void freeGraphics();
+public:
+ static Inventory *init(SherlockEngine *vm);
+ Inventory(SherlockEngine *vm);
+ virtual ~Inventory();
+
+ /**
+ * Free inventory data
+ */
+ void freeInv();
+
+ /**
+ * Load the list of names of graphics for the inventory
+ */
+ void loadGraphics();
+
+ /**
+ * Searches through the list of names that correspond to the inventory items
+ * and returns the number that matches the passed name
+ */
+ int findInv(const Common::String &name);
+
+ /**
+ * Adds a shape from the scene to the player's inventory
+ */
+ int putNameInInventory(const Common::String &name);
+
+ /**
+ * 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 putItemInInventory(Object &obj);
+
+ /**
+ * Deletes a specified item from the player's inventory
+ */
+ int deleteItemFromInventory(const Common::String &name);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv() = 0;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp
new file mode 100644
index 0000000000..9441d6682a
--- /dev/null
+++ b/engines/sherlock/journal.cpp
@@ -0,0 +1,706 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/journal.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_journal.h"
+
+namespace Sherlock {
+
+Journal *Journal::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelJournal(vm);
+ else
+ return new Tattoo::TattooJournal(vm);
+}
+
+Journal::Journal(SherlockEngine *vm) : _vm(vm) {
+ _up = _down = false;
+ _index = 0;
+ _page = 0;
+ _maxPage = 0;
+ _sub = 0;
+}
+
+bool Journal::drawJournal(int direction, int howFar) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int yp = 37;
+ int startPage = _page;
+ bool endJournal = false;
+ bool firstOccurance = true;
+ bool searchSuccessful = false;
+ bool endFlag = false;
+ int lineNum = 0;
+ int savedIndex;
+ int temp;
+ const char *matchP;
+ int width;
+
+ talk._converseNum = -1;
+ _down = true;
+
+ do {
+ // Get the number of lines for the current journal entry
+ loadJournalFile(false);
+ if (_lines.empty()) {
+ // Entry has no text, so it must be a stealth eny. Move onto further journal entries
+ // until an entry with text is found
+ if (++_index == (int)_journal.size()) {
+ endJournal = true;
+ } else {
+ _sub = 0;
+ loadJournalFile(false);
+ }
+ }
+ } while (!endJournal && _lines.empty());
+
+ // Check if there no further pages with text until the end of the journal
+ if (endJournal) {
+ // If moving forward or backwards, clear the page before printing
+ if (direction)
+ drawFrame();
+
+ screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, "Page %d", _page);
+ return false;
+ }
+
+ // If the journal page is being changed, set the wait cursor
+ if (direction)
+ events.setCursor(WAIT);
+
+ switch (direction) {
+ case 1:
+ case 4:
+ // Move backwards howFar number of lines unless either the start of the journal is reached,
+ // or a searched for keyword is found
+ do {
+ // Move backwards through the journal file a line at a time
+ if (--_sub < 0) {
+ do {
+ if (--_index < 0) {
+ _index = 0;
+ _sub = 0;
+ endJournal = true;
+ }
+ else {
+ loadJournalFile(false);
+ _sub = _lines.size() - 1;
+ }
+ } while (!endJournal && _lines.empty());
+ }
+
+ // If it's search mode, check each line for the given keyword
+ if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) {
+ Common::String line = _lines[_sub];
+ line.toUppercase();
+ if (strstr(line.c_str(), _find.c_str()) != nullptr) {
+ // Found a match. Reset howFar so that the start of page that the match
+ // was found on will be displayed
+ searchSuccessful = true;
+ howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE;
+ }
+ }
+
+ ++lineNum;
+ } while (lineNum < howFar && !endJournal);
+
+ if (!_index && !_sub)
+ _page = 1;
+ else
+ _page -= howFar / LINES_PER_PAGE;
+ break;
+
+ case 2:
+ case 3:
+ // Move howFar lines ahead unless the end of the journal is reached,
+ // or a searched for keyword is found
+ for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) {
+ // Handle animating mouse cursor
+ int cursorNum = (int)events.getCursor() + 1;
+ if (cursorNum >(WAIT + 2))
+ cursorNum = WAIT;
+ events.setCursor((CursorId)cursorNum);
+
+ lineNum = 0;
+ savedIndex = _index;
+ int savedSub = _sub;
+
+ // Move a single page ahead
+ do {
+ // If in search mode, check for keyword
+ if (direction >= 3 && _page != startPage) {
+ Common::String line = _lines[_sub];
+ line.toUppercase();
+ if (strstr(line.c_str(), _find.c_str()) != nullptr)
+ searchSuccessful = true;
+ }
+
+ // Move forwards a line at a time, unless search word was found
+ if (!searchSuccessful) {
+ if (++_sub == (int)_lines.size()) {
+ // Reached end of page
+ do {
+ if (++_index == (int)_journal.size()) {
+ _index = savedIndex;
+ _sub = savedSub;
+ loadJournalFile(false);
+ endJournal = true;
+ } else {
+ _sub = 0;
+ loadJournalFile(false);
+ }
+ } while (!endJournal && _lines.empty());
+ }
+
+ ++lineNum;
+ }
+ } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful);
+
+ if (!endJournal && !searchSuccessful)
+ // Move to next page
+ ++_page;
+
+ if (searchSuccessful) {
+ // Search found, so show top of the page it was found on
+ _index = savedIndex;
+ _sub = savedSub;
+ loadJournalFile(false);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (direction) {
+ events.setCursor(ARROW);
+ drawFrame();
+ }
+
+ Common::String fixedText_Page = IS_SERRATED_SCALPEL ? fixedText.getText(Scalpel::kFixedText_Journal_Page) : "TODO";
+
+ screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, fixedText_Page.c_str(), _page);
+
+ temp = _sub;
+ savedIndex = _index;
+ lineNum = 0;
+
+ do {
+ bool inc = true;
+
+ // If there wasn't any line to print at the top of the page, we won't need to
+ // increment the y position
+ if (_lines[temp].empty() && yp == 37)
+ inc = false;
+
+ // If there's a searched for keyword in the line, it will need to be highlighted
+ if (searchSuccessful && firstOccurance) {
+ // Check if line has the keyword
+ Common::String line = _lines[temp];
+ line.toUppercase();
+ if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) {
+ matchP = _lines[temp].c_str() + (matchP - line.c_str());
+ firstOccurance = false;
+
+ // Print out the start of the line before the matching keyword
+ Common::String lineStart(_lines[temp].c_str(), matchP);
+ if (lineStart.hasPrefix("@")) {
+ width = screen.stringWidth(lineStart.c_str() + 1);
+ screen.gPrint(Common::Point(53, yp), 15, "%s", lineStart.c_str() + 1);
+ } else {
+ width = screen.stringWidth(lineStart.c_str());
+ screen.gPrint(Common::Point(53, yp), COL_PEN_COLOR, "%s", lineStart.c_str());
+ }
+
+ // Print out the found keyword
+ Common::String lineMatch(matchP, matchP + _find.size());
+ byte fgColor = IS_SERRATED_SCALPEL ? (byte)Scalpel::INV_FOREGROUND : (byte)Tattoo::INV_FOREGROUND;
+ screen.gPrint(Common::Point(53 + width, yp), fgColor, "%s", lineMatch.c_str());
+ width += screen.stringWidth(lineMatch.c_str());
+
+ // Print remainder of line
+ screen.gPrint(Common::Point(53 + width, yp), COL_PEN_COLOR, "%s", matchP + _find.size());
+ } else if (_lines[temp].hasPrefix("@")) {
+ screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1);
+ } else {
+ screen.gPrint(Common::Point(53, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+ }
+ } else {
+ if (_lines[temp].hasPrefix("@")) {
+ screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1);
+ } else {
+ screen.gPrint(Common::Point(53, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str());
+ }
+ }
+
+ if (++temp == (int)_lines.size()) {
+ // Move to next page
+ do {
+ if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) {
+ ++_index;
+ loadJournalFile(false);
+ temp = 0;
+ } else {
+ if (_index == ((int)_journal.size() - 1))
+ _down = false;
+ endFlag = true;
+ }
+ } while (!endFlag && _lines.empty());
+ }
+
+ if (inc) {
+ // Move to next line
+ ++lineNum;
+ yp += 13;
+ }
+ } while (lineNum < LINES_PER_PAGE && !endFlag);
+
+ _index = savedIndex;
+ _up = _index || _sub;
+
+ return direction >= 3 && searchSuccessful;
+}
+
+void Journal::loadJournalFile(bool alreadyLoaded) {
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ JournalEntry &journalEntry = _journal[_index];
+ const byte *opcodes = talk._opcodes;
+
+ Common::String dirFilename = _directory[journalEntry._converseNum];
+ bool replyOnly = journalEntry._replyOnly;
+
+ // Get the location number from within the filename
+ Common::String locStr(dirFilename.c_str() + 4, dirFilename.c_str() + 6);
+ int newLocation = atoi(locStr.c_str());
+
+ // If not flagged as already loaded, load the conversation into script variables
+ if (!alreadyLoaded) {
+ // See if the file to be used is already loaded
+ if (journalEntry._converseNum != talk._converseNum) {
+ // Nope. Free any previously loaded talk
+ talk.freeTalkVars();
+
+ // Find the person being referred to
+ talk._talkTo = -1;
+ for (int idx = 0; idx < (int)people._characters.size(); ++idx) {
+ Common::String portrait = people._characters[idx]._portrait;
+ Common::String numStr(portrait.c_str(), portrait.c_str() + 4);
+
+ if (locStr == numStr) {
+ talk._talkTo = idx;
+ break;
+ }
+ }
+
+ // Load their talk file
+ talk.loadTalkFile(dirFilename);
+ }
+ }
+
+ if (talk[0]._statement.hasPrefix("*") || talk[0]._statement.hasPrefix("^"))
+ replyOnly = true;
+
+ // If this isn't the first journal entry, see if the previous journal entry
+ // was in the same scene to see if we need to include the scene header
+ int oldLocation = -1;
+ if (_index != 0) {
+ // Get the scene number of the prior journal entry
+ Common::String priorEntry = _directory[_journal[_index - 1]._converseNum];
+ oldLocation = atoi(Common::String(priorEntry.c_str() + 4, priorEntry.c_str() + 6).c_str());
+ }
+
+ // Start building journal string
+ Statement &statement = talk[journalEntry._statementNum];
+ Common::String journalString;
+
+ if (newLocation != oldLocation) {
+ // Add in scene title
+ journalString = "@";
+ if (IS_SERRATED_SCALPEL || newLocation - 1 < 100)
+ journalString += _locations[newLocation - 1];
+ journalString += ":";
+
+ // See if title can fit into a single line, or requires splitting on 2 lines
+ int width = screen.stringWidth(journalString.c_str() + 1);
+ if (width > JOURNAL_MAX_WIDTH) {
+ // Scan backwards from end of title to find a space between a word
+ // where the width is less than the maximum allowed for the line
+ const char *lineP = journalString.c_str() + journalString.size() - 1;
+ while (width > JOURNAL_MAX_WIDTH || *lineP != ' ')
+ width -= screen.charWidth(*lineP--);
+
+ // Split the header into two lines, and add a '@' prefix
+ // to the second line as well
+ journalString = Common::String(journalString.c_str(), lineP) + "\n@" +
+ Common::String(lineP + 1);
+ }
+
+ // Add a newline at the end of the title
+ journalString += '\n';
+ }
+
+ // If Holmes has something to say first, then take care of it
+ if (!replyOnly) {
+ // Handle the grammar
+ journalString += "Holmes ";
+ if (talk[journalEntry._statementNum]._statement.hasSuffix("?"))
+ journalString += "asked ";
+ else
+ journalString += "said to ";
+
+ if (talk._talkTo == 1) {
+ journalString += "me";
+ } else if ((talk._talkTo == 2 && IS_SERRATED_SCALPEL) || (talk._talkTo == 18 && IS_ROSE_TATTOO)) {
+ journalString += "the Inspector";
+ } else {
+ journalString += people._characters[talk._talkTo]._name;
+ }
+ journalString += ", \"";
+
+ // Add the statement
+ journalString += statement._statement;
+ }
+
+ // Handle including the reply
+ bool startOfReply = true;
+ bool ctrlSpace = false;
+ bool commentFlag = false;
+ bool commentJustPrinted = false;
+ const byte *replyP = (const byte *)statement._reply.c_str();
+ const int inspectorId = (IS_SERRATED_SCALPEL) ? 2 : 18;
+
+ while (*replyP) {
+ byte c = *replyP++;
+
+ if (IS_ROSE_TATTOO) {
+ // Ignore commented out data
+ if (c == '/' && *(replyP + 1) == '*') {
+ replyP++; // skip *
+ while (*replyP++ != '*') {} // empty loop on purpose
+ replyP++; // skip /
+ c = *replyP;
+ }
+ }
+
+ // Is it a control character?
+ if (c < opcodes[0]) {
+ // Nope. Set flag for allowing control codes to insert spaces
+ ctrlSpace = true;
+ assert(c >= ' ');
+
+ // Check for embedded comments
+ if (c == '{' || c == '}') {
+
+ // TODO: Rose Tattoo checks if no text was added for the last
+ // comment here. In such a case, the last "XXX said" string is
+ // removed here.
+
+ // Comment characters. If we're starting a comment and there's
+ // already text displayed, add a closing quote
+ if (c == '{' && !startOfReply && !commentJustPrinted)
+ journalString += '"';
+
+ // If a reply isn't just being started, and we didn't just end
+ // a comment (which would have added a line), add a carriage return
+ if (!startOfReply && ((!commentJustPrinted && c == '{') || c == '}'))
+ journalString += '\n';
+ startOfReply = false;
+
+ // Handle setting or clearing comment state
+ if (c == '{') {
+ commentFlag = true;
+ commentJustPrinted = false;
+ } else {
+ commentFlag = false;
+ commentJustPrinted = true;
+ }
+ } else {
+ if (startOfReply) {
+ if (!replyOnly) {
+ journalString += "\"\n";
+
+ if (talk._talkTo == 1)
+ journalString += "I replied, \"";
+ else
+ journalString += "The reply was, \"";
+ } else {
+ if (talk._talkTo == 1)
+ journalString += "I";
+ else if (talk._talkTo == inspectorId)
+ journalString += "The Inspector";
+ else
+ journalString += people._characters[talk._talkTo]._name;
+
+ const byte *strP = replyP + 1;
+ byte v;
+ do {
+ v = *strP++;
+ } while (v && (v < opcodes[0]) && (v != '.') && (v != '!') && (v != '?'));
+
+ if (v == '?')
+ journalString += " asked, \"";
+ else
+ journalString += " said, \"";
+ }
+
+ startOfReply = false;
+ }
+
+ // Copy text from the place until either the reply ends, a comment
+ // {} block is started, or a control character is encountered
+ journalString += c;
+ do {
+ journalString += *replyP++;
+ } while (*replyP && *replyP < opcodes[0] && *replyP != '{' && *replyP != '}');
+
+ commentJustPrinted = false;
+ }
+ } else if (c == opcodes[OP_SWITCH_SPEAKER]) {
+ if (!startOfReply) {
+ if (!commentFlag && !commentJustPrinted)
+ journalString += "\"\n";
+
+ journalString += "Then ";
+ commentFlag = false;
+ } else if (!replyOnly) {
+ journalString += "\"\n";
+ }
+
+ startOfReply = false;
+ c = *replyP++ - 1;
+ if (IS_ROSE_TATTOO)
+ replyP++;
+
+ if (c == 0)
+ journalString += "Holmes";
+ else if (c == 1)
+ journalString += "I";
+ else if (c == inspectorId)
+ journalString += "the Inspector";
+ else
+ journalString += people._characters[c]._name;
+
+ const byte *strP = replyP;
+ byte v;
+ do {
+ v = *strP++;
+ } while (v && v < opcodes[0] && v != '.' && v != '!' && v != '?');
+
+ if (v == '?')
+ journalString += " asked, \"";
+ else
+ journalString += " said, \"";
+ } else {
+ if (IS_SERRATED_SCALPEL) {
+ // Control code, so move past it and any parameters
+ if (c == opcodes[OP_RUN_CANIMATION] ||
+ c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
+ c == opcodes[OP_PAUSE] ||
+ c == opcodes[OP_PAUSE_WITHOUT_CONTROL] ||
+ c == opcodes[OP_WALK_TO_CANIMATION]) {
+ // These commands have a single parameter
+ ++replyP;
+ } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) {
+ replyP += (replyP[0] & 127) + replyP[1] + 2;
+ } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) {
+ replyP += 4;
+ } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) {
+ replyP += 2;
+ } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] ||
+ c == opcodes[OP_CALL_TALK_FILE]) {
+ replyP += 8;
+ break;
+ } else if (
+ c == opcodes[OP_TOGGLE_OBJECT] ||
+ c == opcodes[OP_ADD_ITEM_TO_INVENTORY] ||
+ c == opcodes[OP_SET_OBJECT] ||
+ c == opcodes[OP_DISPLAY_INFO_LINE] ||
+ c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) {
+ replyP += (*replyP & 127) + 1;
+ } else if (c == opcodes[OP_GOTO_SCENE]) {
+ replyP += 5;
+ } else if (c == opcodes[OP_END_TEXT_WINDOW]) {
+ journalString += "\n";
+ }
+ } else {
+ if (c == opcodes[OP_RUN_CANIMATION] ||
+ c == opcodes[OP_PAUSE] ||
+ c == opcodes[OP_MOUSE_OFF_ON] ||
+ c == opcodes[OP_SET_WALK_CONTROL] ||
+ c == opcodes[OP_PAUSE_WITHOUT_CONTROL] ||
+ c == opcodes[OP_WALK_TO_CANIMATION] ||
+ c == opcodes[OP_TURN_NPC_OFF] ||
+ c == opcodes[OP_TURN_NPC_ON] ||
+ c == opcodes[OP_RESTORE_PEOPLE_SEQUENCE])
+ ++replyP;
+ else if (
+ c == opcodes[OP_SET_TALK_SEQUENCE] ||
+ c == opcodes[OP_SET_FLAG] ||
+ c == opcodes[OP_WALK_NPC_TO_CANIM] ||
+ c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_CANIM] ||
+ c == opcodes[OP_NPC_PATH_LABEL] ||
+ c == opcodes[OP_PATH_GOTO_LABEL])
+ replyP += 2;
+ else if (
+ c == opcodes[OP_SET_NPC_PATH_PAUSE] ||
+ c == opcodes[OP_NPC_PATH_PAUSE_TAKING_NOTES] ||
+ c == opcodes[OP_NPC_PATH_PAUSE_LOOKING_HOLMES] ||
+ c == opcodes[OP_NPC_VERB_CANIM])
+ replyP += 3;
+ else if (
+ c == opcodes[OP_SET_SCENE_ENTRY_FLAG] ||
+ c == opcodes[OP_PATH_IF_FLAG_GOTO_LABEL])
+ replyP += 4;
+ else if (
+ c == opcodes[OP_WALK_TO_COORDS])
+ replyP += 5;
+ else if (
+ c == opcodes[OP_WALK_NPC_TO_COORDS] ||
+ c == opcodes[OP_GOTO_SCENE] ||
+ c == opcodes[OP_SET_NPC_PATH_DEST] ||
+ c == opcodes[OP_SET_NPC_POSITION])
+ replyP += 6;
+ else if (
+ c == opcodes[OP_PLAY_SONG] ||
+ c == opcodes[OP_NEXT_SONG])
+ replyP += 8;
+ else if (
+ c == opcodes[OP_CALL_TALK_FILE] ||
+ c == opcodes[OP_SET_NPC_TALK_FILE] ||
+ c == opcodes[OP_NPC_WALK_GRAPHICS])
+ replyP += 9;
+ else if (
+ c == opcodes[OP_NPC_VERB_SCRIPT])
+ replyP += 10;
+ else if (
+ c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_COORDS])
+ replyP += 11;
+ else if (
+ c == opcodes[OP_NPC_VERB] ||
+ c == opcodes[OP_NPC_VERB_TARGET])
+ replyP += 14;
+ else if (
+ c == opcodes[OP_ADJUST_OBJ_SEQUENCE])
+ replyP += (replyP[0] & 127) + replyP[1] + 2;
+ else if (
+ c == opcodes[OP_TOGGLE_OBJECT] ||
+ c == opcodes[OP_ADD_ITEM_TO_INVENTORY] ||
+ c == opcodes[OP_SET_OBJECT] ||
+ c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY])
+ replyP += (*replyP & 127) + 1;
+ else if (
+ c == opcodes[OP_END_TEXT_WINDOW]) {
+ journalString += '\n';
+ } else if (
+ c == opcodes[OP_NPC_DESC_ON_OFF]) {
+ replyP++;
+ while (replyP[0] && replyP[0] != opcodes[OP_NPC_DESC_ON_OFF])
+ replyP++;
+ replyP++;
+ } else if (
+ c == opcodes[OP_SET_NPC_INFO_LINE])
+ replyP += replyP[1] + 2;
+ }
+
+ // Put a space in the output for a control character, unless it's
+ // immediately coming after another control character
+ if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_END_TEXT_WINDOW] &&
+ !commentJustPrinted) {
+ journalString += " ";
+ ctrlSpace = false;
+ }
+ }
+ }
+
+ if (!startOfReply && !commentJustPrinted)
+ journalString += '"';
+
+ // Finally finished building the journal text. Need to process the text to
+ // word wrap it to fit on-screen. The resulting lines are stored in the
+ // _lines array
+ _lines.clear();
+
+ while (!journalString.empty()) {
+ const char *startP = journalString.c_str();
+
+ // If the first character is a '@' flagging a title line, then move
+ // past it, so the @ won't be included in the line width calculation
+ if (*startP == '@')
+ ++startP;
+
+ // Build up chacters until a full line is found
+ int width = 0;
+ const char *endP = startP;
+ while (width < JOURNAL_MAX_WIDTH && *endP && *endP != '\n' && (endP - startP) < (JOURNAL_MAX_CHARS - 1))
+ width += screen.charWidth(*endP++);
+
+ // If word wrapping, move back to end of prior word
+ if (width >= JOURNAL_MAX_WIDTH || (endP - startP) >= (JOURNAL_MAX_CHARS - 1)) {
+ while (*--endP != ' ')
+ ;
+ }
+
+ // Add in the line
+ _lines.push_back(Common::String(journalString.c_str(), endP));
+
+ // Strip line off from string being processed
+ journalString = *endP ? Common::String(endP + 1) : "";
+ }
+
+ // Add a blank line at the end of the text as long as text was present
+ if (!startOfReply) {
+ _lines.push_back("");
+ } else {
+ _lines.clear();
+ }
+}
+
+void Journal::synchronize(Serializer &s) {
+ s.syncAsSint16LE(_index);
+ s.syncAsSint16LE(_sub);
+ s.syncAsSint16LE(_page);
+ s.syncAsSint16LE(_maxPage);
+
+ int journalCount = _journal.size();
+ s.syncAsUint16LE(journalCount);
+ if (s.isLoading())
+ _journal.resize(journalCount);
+
+ for (uint idx = 0; idx < _journal.size(); ++idx) {
+ JournalEntry &je = _journal[idx];
+
+ s.syncAsSint16LE(je._converseNum);
+ s.syncAsByte(je._replyOnly);
+ s.syncAsSint16LE(je._statementNum);
+ }
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/journal.h b/engines/sherlock/journal.h
new file mode 100644
index 0000000000..d2baae11a9
--- /dev/null
+++ b/engines/sherlock/journal.h
@@ -0,0 +1,105 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_JOURNAL_H
+#define SHERLOCK_JOURNAL_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "common/stream.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+#define LINES_PER_PAGE (IS_SERRATED_SCALPEL ? 11 : 17)
+
+class SherlockEngine;
+
+struct JournalEntry {
+ int _converseNum;
+ bool _replyOnly;
+ int _statementNum;
+
+ JournalEntry() : _converseNum(0), _replyOnly(false), _statementNum(0) {}
+ JournalEntry(int converseNum, int statementNum, bool replyOnly = false) :
+ _converseNum(converseNum), _statementNum(statementNum), _replyOnly(replyOnly) {}
+};
+
+class Journal {
+protected:
+ SherlockEngine *_vm;
+ Common::StringArray _directory;
+ Common::StringArray _locations;
+ Common::Array<JournalEntry> _journal;
+ Common::StringArray _lines;
+ bool _up, _down;
+ int _index;
+ int _page;
+ int _maxPage;
+ int _sub;
+ Common::String _find;
+
+ Journal(SherlockEngine *vm);
+
+ /**
+ * Loads the description for the current display index in the journal, and then
+ * word wraps the result to prepare it for being displayed
+ * @param alreadyLoaded Indicates whether the journal file is being loaded for the
+ * first time, or being reloaded
+ */
+ void loadJournalFile(bool alreadyLoaded);
+public:
+ static Journal *init(SherlockEngine *vm);
+ virtual ~Journal() {}
+
+ /**
+ * Displays a page of the journal at the current index
+ */
+ bool drawJournal(int direction, int howFar);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+public:
+ /**
+ * Draw the journal background, frame, and interface buttons
+ */
+ virtual void drawFrame() = 0;
+
+ /**
+ * Records statements that are said, in the order which they are said. The player
+ * can then read the journal to review them
+ */
+ virtual void record(int converseNum, int statementNum, bool replyOnly = false) {}
+
+ /**
+ * Reset viewing position to the start of the journal
+ */
+ virtual void resetPosition() {}
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/map.cpp b/engines/sherlock/map.cpp
new file mode 100644
index 0000000000..fbccaf244f
--- /dev/null
+++ b/engines/sherlock/map.cpp
@@ -0,0 +1,52 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "sherlock/map.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/tattoo/tattoo_map.h"
+
+namespace Sherlock {
+
+Map *Map::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelMap(vm);
+ else
+ return new Tattoo::TattooMap(vm);
+}
+
+Map::Map(SherlockEngine *vm) : _vm(vm) {
+ _charPoint = _oldCharPoint = 0;
+ _active = _frameChangeFlag = false;
+}
+
+void Map::synchronize(Serializer &s) {
+ s.syncAsSint32LE(_bigPos.x);
+ s.syncAsSint32LE(_bigPos.y);
+ s.syncAsSint32LE(_overPos.x);
+ s.syncAsSint16LE(_overPos.y);
+ s.syncAsSint16LE(_oldCharPoint);
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/map.h b/engines/sherlock/map.h
new file mode 100644
index 0000000000..104f5e9c8a
--- /dev/null
+++ b/engines/sherlock/map.h
@@ -0,0 +1,61 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_MAP_H
+#define SHERLOCK_MAP_H
+
+#include "sherlock/objects.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+class Map {
+protected:
+ SherlockEngine *_vm;
+
+ Map(SherlockEngine *vm);
+public:
+ Point32 _overPos;
+ Point32 _bigPos;
+ int _charPoint, _oldCharPoint;
+ bool _active;
+ bool _frameChangeFlag;
+public:
+ static Map *init(SherlockEngine *vm);
+ virtual ~Map() {}
+
+ /**
+ * Show the map
+ */
+ virtual int show() = 0;
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk
new file mode 100644
index 0000000000..32c5d3acc3
--- /dev/null
+++ b/engines/sherlock/module.mk
@@ -0,0 +1,71 @@
+MODULE := engines/sherlock
+
+MODULE_OBJS = \
+ scalpel/darts.o \
+ scalpel/scalpel.o \
+ scalpel/3do/movie_decoder.o \
+ scalpel/drivers/adlib.o \
+ scalpel/drivers/mt32.o \
+ scalpel/tsage/logo.o \
+ scalpel/tsage/resources.o \
+ scalpel/scalpel_debugger.o \
+ scalpel/scalpel_fixed_text.o \
+ scalpel/scalpel_inventory.o \
+ scalpel/scalpel_journal.o \
+ scalpel/scalpel_map.o \
+ scalpel/scalpel_people.o \
+ scalpel/scalpel_saveload.o \
+ scalpel/scalpel_scene.o \
+ scalpel/scalpel_screen.o \
+ scalpel/scalpel_talk.o \
+ scalpel/scalpel_user_interface.o \
+ scalpel/settings.o \
+ tattoo/tattoo.o \
+ tattoo/tattoo_darts.o \
+ tattoo/tattoo_debugger.o \
+ tattoo/tattoo_fixed_text.o \
+ tattoo/tattoo_inventory.o \
+ tattoo/tattoo_journal.o \
+ tattoo/tattoo_map.o \
+ tattoo/tattoo_people.o \
+ tattoo/tattoo_resources.o \
+ tattoo/tattoo_scene.o \
+ tattoo/tattoo_talk.o \
+ tattoo/tattoo_user_interface.o \
+ tattoo/widget_base.o \
+ tattoo/widget_inventory.o \
+ tattoo/widget_lab.o \
+ tattoo/widget_talk.o \
+ tattoo/widget_text.o \
+ tattoo/widget_tooltip.o \
+ tattoo/widget_verbs.o \
+ animation.o \
+ debugger.o \
+ detection.o \
+ events.o \
+ fixed_text.o \
+ fonts.o \
+ image_file.o \
+ inventory.o \
+ journal.o \
+ map.o \
+ music.o \
+ objects.o \
+ people.o \
+ resources.o \
+ saveload.o \
+ scene.o \
+ screen.o \
+ sherlock.o \
+ sound.o \
+ surface.o \
+ talk.o \
+ user_interface.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_SHERLOCK), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp
new file mode 100644
index 0000000000..49c48126f4
--- /dev/null
+++ b/engines/sherlock/music.cpp
@@ -0,0 +1,598 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/config-manager.h"
+#include "common/mutex.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/music.h"
+#include "sherlock/scalpel/drivers/mididriver.h"
+// for Miles Audio (Sherlock Holmes 2)
+#include "audio/miles.h"
+// for 3DO digital music
+#include "audio/decoders/aiff.h"
+
+namespace Sherlock {
+
+#define NUM_SONGS 45
+
+/* This tells which song to play in each room, 0 = no song played */
+static const char ROOM_SONG[62] = {
+ 0, 20, 43, 6, 11, 2, 8, 15, 6, 28,
+ 6, 38, 7, 32, 16, 5, 8, 41, 9, 22,
+ 10, 23, 4, 39, 19, 24, 13, 27, 0, 30,
+ 3, 21, 26, 25, 16, 29, 1, 1, 18, 12,
+ 1, 17, 17, 31, 17, 34, 36, 7, 20, 20,
+ 33, 8, 44, 40, 42, 35, 0, 0, 0, 12,
+ 12
+};
+
+static const char *const SONG_NAMES[NUM_SONGS] = {
+ "SINGERF", "CHEMIST", "TOBAC", "EQUEST", "MORTUARY", "DOCKS", "LSTUDY",
+ "LORD", "BOY", "PERFUM1", "BAKER1", "BAKER2", "OPERA1", "HOLMES",
+ "FFLAT", "OP1FLAT", "ZOO", "SROOM", "FLOWERS", "YARD", "TAXID",
+ "PUB1", "VICTIM", "RUGBY", "DORM", "SHERMAN", "LAWYER", "THEATRE",
+ "DETECT", "OPERA4", "POOL", "SOOTH", "ANNA1", "ANNA2", "PROLOG3",
+ "PAWNSHOP", "MUSICBOX", "MOZART1", "ROBHUNT", "PANCRAS1", "PANCRAS2", "LORDKILL",
+ "BLACKWEL", "RESCUE", "MAP"
+};
+
+MidiParser_SH::MidiParser_SH() {
+ _ppqn = 1;
+ setTempo(16667);
+ _data = nullptr;
+ _beats = 0;
+ _lastEvent = 0;
+ _trackEnd = nullptr;
+
+ _musData = nullptr;
+ _musDataSize = 0;
+}
+
+MidiParser_SH::~MidiParser_SH() {
+ Common::StackLock lock(_mutex);
+ unloadMusic();
+ _driver = NULL;
+}
+
+void MidiParser_SH::parseNextEvent(EventInfo &info) {
+ Common::StackLock lock(_mutex);
+
+// warning("parseNextEvent");
+
+ // there is no delta right at the start of the music data
+ // this order is essential, otherwise notes will get delayed or even go missing
+ if (_position._playPos != _tracks[0]) {
+ info.delta = *(_position._playPos++);
+ } else {
+ info.delta = 0;
+ }
+
+ info.start = _position._playPos;
+
+ info.event = *_position._playPos++;
+ //warning("Event %x", info.event);
+ _position._runningStatus = info.event;
+
+ switch (info.command()) {
+ case 0xC: { // program change
+ int idx = *_position._playPos++;
+ info.basic.param1 = idx & 0x7f;
+ info.basic.param2 = 0;
+ }
+ break;
+ case 0xD:
+ info.basic.param1 = *_position._playPos++;
+ info.basic.param2 = 0;
+ break;
+
+ case 0xB:
+ info.basic.param1 = *_position._playPos++;
+ info.basic.param2 = *_position._playPos++;
+ info.length = 0;
+ break;
+
+ case 0x8:
+ case 0x9:
+ case 0xA:
+ case 0xE:
+ info.basic.param1 = *(_position._playPos++);
+ info.basic.param2 = *(_position._playPos++);
+ if (info.command() == 0x9 && info.basic.param2 == 0) {
+ // NoteOn with param2==0 is a NoteOff
+ info.event = info.channel() | 0x80;
+ }
+ info.length = 0;
+ break;
+ case 0xF:
+ if (info.event == 0xFF) {
+ error("SysEx META event 0xFF");
+
+ byte type = *(_position._playPos++);
+ switch(type) {
+ case 0x2F:
+ // End of Track
+ allNotesOff();
+ stopPlaying();
+ unloadMusic();
+ return;
+ case 0x51:
+ warning("TODO: 0xFF / 0x51");
+ return;
+ default:
+ warning("TODO: 0xFF / %x Unknown", type);
+ break;
+ }
+ } else if (info.event == 0xFC) {
+ // Official End-Of-Track signal
+ debugC(kDebugLevelMusic, "Music: System META event 0xFC");
+
+ byte type = *(_position._playPos++);
+ switch (type) {
+ case 0x80: // end of track, triggers looping
+ debugC(kDebugLevelMusic, "Music: META event triggered looping");
+ jumpToTick(0, true, true, false);
+ break;
+ case 0x81: // end of track, stop playing
+ debugC(kDebugLevelMusic, "Music: META event triggered music stop");
+ stopPlaying();
+ unloadMusic();
+ break;
+ default:
+ error("MidiParser_SH::parseNextEvent: Unknown META event 0xFC type %x", type);
+ break;
+ }
+ } else {
+ warning("TODO: %x / Unknown", info.event);
+ break;
+ }
+ break;
+ default:
+ warning("MidiParser_SH::parseNextEvent: Unsupported event code %x", info.event);
+ break;
+ }// switch (info.command())
+}
+
+bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) {
+ Common::StackLock lock(_mutex);
+
+ debugC(kDebugLevelMusic, "Music: loadMusic()");
+ unloadMusic();
+
+ _musData = musData;
+ _musDataSize = musDataSize;
+
+ byte *headerPtr = _musData + 12; // skip over the already checked SPACE header
+ byte *pos = headerPtr;
+
+ uint16 headerSize = READ_LE_UINT16(headerPtr);
+ assert(headerSize == 0x7F); // Security check
+
+ // Skip over header
+ pos += headerSize;
+
+ _lastEvent = 0;
+ _trackEnd = _musData + _musDataSize;
+
+ _numTracks = 1;
+ _tracks[0] = pos;
+
+ _ppqn = 1;
+ setTempo(16667);
+ setTrack(0);
+
+ return true;
+}
+
+void MidiParser_SH::unloadMusic() {
+ Common::StackLock lock(_mutex);
+
+ if (_musData) {
+ delete[] _musData;
+ _musData = NULL;
+ _musDataSize = 0;
+ }
+
+ MidiParser::unloadMusic();
+}
+
+/*----------------------------------------------------------------*/
+
+Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
+ _midiDriver = NULL;
+ _midiParser = NULL;
+ _musicType = MT_NULL;
+ _musicPlaying = false;
+ _musicOn = false;
+ _midiOption = false;
+ _musicVolume = 0;
+
+ if (IS_3DO) {
+ // 3DO - uses digital samples for music
+ _musicOn = true;
+ return;
+ }
+
+ if (_vm->_interactiveFl)
+ _vm->_res->addToCache("MUSIC.LIB");
+
+ MidiDriver::DeviceHandle dev;
+
+ if (IS_SERRATED_SCALPEL) {
+ // Serrated Scalpel: used an internal Electronic Arts .MUS music engine
+ _midiParser = new MidiParser_SH();
+ dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32);
+ _musicType = MidiDriver::getMusicType(dev);
+
+ switch (_musicType) {
+ case MT_ADLIB:
+ _midiDriver = MidiDriver_SH_AdLib_create();
+ break;
+ case MT_MT32:
+ _midiDriver = MidiDriver_MT32_create();
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _midiDriver = MidiDriver_MT32_create();
+ _musicType = MT_MT32;
+ }
+ break;
+ default:
+ // Create default one
+ // I guess we shouldn't do this anymore
+ //_midiDriver = MidiDriver::createMidi(dev);
+ break;
+ }
+ } else {
+ // Rose Tattooo: seems to use Miles Audio 3
+ _midiParser = MidiParser::createParser_XMIDI();
+ dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
+ _musicType = MidiDriver::getMusicType(dev);
+
+ switch (_musicType) {
+ case MT_ADLIB:
+ // SAMPLE.AD -> regular AdLib instrument data
+ // SAMPLE.OPL -> OPL-3 instrument data
+ // although in case of Rose Tattoo both files are exactly the same
+ _midiDriver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL");
+ break;
+ case MT_MT32:
+ // Sherlock Holmes 2 does not have a MT32 timbre file
+ _midiDriver = Audio::MidiDriver_Miles_MT32_create("");
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _midiDriver = Audio::MidiDriver_Miles_MT32_create("");
+ _musicType = MT_MT32;
+ } else {
+ _midiDriver = MidiDriver::createMidi(dev);
+ _musicType = MT_GM;
+ }
+ break;
+ default:
+ // Do not create anything
+ break;
+ }
+ }
+
+ if (_midiDriver) {
+ int ret = _midiDriver->open();
+ if (ret == 0) {
+ // Reset is done inside our MIDI driver
+ _midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback);
+ }
+ _midiParser->setMidiDriver(_midiDriver);
+ _midiParser->setTimerRate(_midiDriver->getBaseTempo());
+
+ if (IS_SERRATED_SCALPEL) {
+ if (_musicType == MT_MT32) {
+ // Upload patches
+ Common::SeekableReadStream *MT32driverStream = _vm->_res->load("MTHOM.DRV", "MUSIC.LIB");
+
+ if (!MT32driverStream)
+ error("Music: could not load MTHOM.DRV, critical");
+
+ byte *MT32driverData = new byte[MT32driverStream->size()];
+ int32 MT32driverDataSize = MT32driverStream->size();
+ assert(MT32driverData);
+
+ MT32driverStream->read(MT32driverData, MT32driverDataSize);
+ delete MT32driverStream;
+
+ assert(MT32driverDataSize > 12);
+ byte *MT32driverDataPtr = MT32driverData + 12;
+ MT32driverDataSize -= 12;
+
+ MidiDriver_MT32_uploadPatches(_midiDriver, MT32driverDataPtr, MT32driverDataSize);
+ delete[] MT32driverData;
+ }
+ }
+
+ _musicOn = true;
+ }
+}
+
+Music::~Music() {
+ stopMusic();
+ if (_midiDriver) {
+ _midiDriver->setTimerCallback(this, NULL);
+ }
+ if (_midiParser) {
+ _midiParser->stopPlaying();
+ delete _midiParser;
+ _midiParser = nullptr;
+ }
+ if (_midiDriver) {
+ _midiDriver->close();
+ delete _midiDriver;
+ }
+}
+
+bool Music::loadSong(int songNumber) {
+ debugC(kDebugLevelMusic, "Music: loadSong()");
+
+ if(songNumber == 100)
+ songNumber = 55;
+ else if(songNumber == 70)
+ songNumber = 54;
+
+ if((songNumber > 60) || (songNumber < 1))
+ return false;
+
+ songNumber = ROOM_SONG[songNumber];
+
+ if(songNumber == 0)
+ songNumber = 12;
+
+ if((songNumber > NUM_SONGS) || (songNumber < 1))
+ return false;
+
+ Common::String songName = Common::String(SONG_NAMES[songNumber - 1]);
+
+ freeSong(); // free any song that is currently loaded
+ stopMusic();
+
+ if (!playMusic(songName))
+ return false;
+
+ startSong();
+ return true;
+}
+
+bool Music::loadSong(const Common::String &songName) {
+ freeSong(); // free any song that is currently loaded
+ stopMusic();
+
+ if (!playMusic(songName))
+ return false;
+
+ startSong();
+ return true;
+}
+
+void Music::syncMusicSettings() {
+ _musicOn = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute");
+}
+
+bool Music::playMusic(const Common::String &name) {
+ if (!_musicOn)
+ return false;
+
+ debugC(kDebugLevelMusic, "Music: playMusic('%s')", name.c_str());
+
+ if (!IS_3DO) {
+ // MIDI based
+ if (!_midiDriver)
+ return false;
+
+ Common::String midiMusicName = (IS_SERRATED_SCALPEL) ? name + ".MUS" : name + ".XMI";
+ Common::SeekableReadStream *stream = _vm->_res->load(midiMusicName, "MUSIC.LIB");
+
+ byte *midiMusicData = new byte[stream->size()];
+ int32 midiMusicDataSize = stream->size();
+
+ stream->read(midiMusicData, midiMusicDataSize);
+ delete stream;
+
+ // for dumping the music tracks
+#if 0
+ Common::DumpFile outFile;
+ outFile.open(name + ".RAW");
+ outFile.write(data, stream->size());
+ outFile.flush();
+ outFile.close();
+#endif
+
+ if (midiMusicDataSize < 14) {
+ warning("Music: not enough data in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+
+ byte *dataPos = midiMusicData;
+ uint32 dataSize = midiMusicDataSize;
+
+ if (IS_SERRATED_SCALPEL) {
+ if (memcmp(" ", dataPos, 12)) {
+ warning("Music: expected header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+ dataPos += 12;
+ dataSize -= 12;
+
+ if (dataSize < 0x7F) {
+ warning("Music: expected music header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+
+ uint16 headerSize = READ_LE_UINT16(dataPos);
+ if (headerSize != 0x7F) {
+ warning("Music: header is not as expected");
+ delete[] midiMusicData;
+ return false;
+ }
+ } else {
+ if (memcmp("FORM", dataPos, 4)) {
+ warning("Music: expected header not found in music file");
+ delete[] midiMusicData;
+ return false;
+ }
+ }
+
+ if (IS_SERRATED_SCALPEL) {
+ // Pass the music data to the driver as well
+ // because channel mapping and a few other things inside the header
+ switch (_musicType) {
+ case MT_ADLIB:
+ MidiDriver_SH_AdLib_newMusicData(_midiDriver, dataPos, dataSize);
+ break;
+
+ case MT_MT32:
+ MidiDriver_MT32_newMusicData(_midiDriver, dataPos, dataSize);
+ break;
+
+ default:
+ // should never happen
+ break;
+ }
+ }
+
+ _midiParser->loadMusic(midiMusicData, midiMusicDataSize);
+
+ } else {
+ // 3DO: sample based
+ Audio::AudioStream *musicStream;
+ Common::String digitalMusicName = "music/" + name + "_MW22.aifc";
+
+ if (isPlaying()) {
+ _mixer->stopHandle(_digitalMusicHandle);
+ }
+
+ Common::File *digitalMusicFile = new Common::File();
+ if (!digitalMusicFile->open(digitalMusicName)) {
+ warning("playMusic: can not open 3DO music '%s'", digitalMusicName.c_str());
+ return false;
+ }
+
+ // Try to load the given file as AIFF/AIFC
+ musicStream = Audio::makeAIFFStream(digitalMusicFile, DisposeAfterUse::YES);
+ if (!musicStream) {
+ warning("playMusic: can not load 3DO music '%s'", digitalMusicName.c_str());
+ return false;
+ }
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_digitalMusicHandle, musicStream);
+ }
+ return true;
+}
+
+void Music::stopMusic() {
+ freeSong();
+}
+
+void Music::startSong() {
+ if (!_musicOn)
+ return;
+
+ // TODO
+ warning("TODO: Sound::startSong");
+ _musicPlaying = true;
+}
+
+void Music::freeSong() {
+ if (!IS_3DO) {
+ if (_midiParser->isPlaying())
+ _midiParser->stopPlaying();
+
+ // Free the MIDI MUS data buffer
+ _midiParser->unloadMusic();
+ }
+
+ _musicPlaying = false;
+}
+
+void Music::waitTimerRoland(uint time) {
+ // TODO
+ warning("TODO: Sound::waitTimerRoland");
+}
+
+bool Music::isPlaying() {
+ if (!IS_3DO) {
+ // MIDI based
+ return _midiParser->isPlaying();
+ } else {
+ // 3DO: sample based
+ return _mixer->isSoundHandleActive(_digitalMusicHandle);
+ }
+}
+
+// Returns the current music position in milliseconds
+uint32 Music::getCurrentPosition() {
+ if (!IS_3DO) {
+ // MIDI based
+ return (_midiParser->getTick() * 1000) / 60; // translate tick to millisecond
+ } else {
+ // 3DO: sample based
+ return _mixer->getSoundElapsedTime(_digitalMusicHandle);
+ }
+}
+
+// This is used to wait for the music in certain situations like especially the intro
+// Note: the original game didn't do this, instead it just waited for certain amounts of time
+// We do this, so that the intro graphics + music work together even on faster/slower hardware.
+bool Music::waitUntilMSec(uint32 msecTarget, uint32 msecMax, uint32 additionalDelay, uint32 noMusicDelay) {
+ uint32 msecCurrent = 0;
+
+ if (!isPlaying()) {
+ return _vm->_events->delay(noMusicDelay, true);
+ }
+ while (1) {
+ if (!isPlaying()) { // Music is not playing anymore -> we are done
+ if (additionalDelay > 0) {
+ if (!_vm->_events->delay(additionalDelay, true))
+ return false;
+ }
+ return true;
+ }
+
+ msecCurrent = getCurrentPosition();
+ //warning("waitUntilMSec: %lx", msecCurrent);
+
+ if ((!msecMax) || (msecCurrent <= msecMax)) {
+ if (msecCurrent >= msecTarget) {
+ if (additionalDelay > 0) {
+ if (!_vm->_events->delay(additionalDelay, true))
+ return false;
+ }
+ return true;
+ }
+ }
+ if (!_vm->_events->delay(10, true))
+ return false;
+ }
+}
+
+void Music::setMIDIVolume(int volume) {
+ warning("TODO: Music::setMIDIVolume");
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h
new file mode 100644
index 0000000000..93c565c162
--- /dev/null
+++ b/engines/sherlock/music.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_MUSIC_H
+#define SHERLOCK_MUSIC_H
+
+#include "audio/midiplayer.h"
+#include "audio/midiparser.h"
+//#include "audio/mididrv.h"
+#include "sherlock/scalpel/drivers/mididriver.h"
+// for 3DO digital music
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "common/mutex.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+class MidiParser_SH : public MidiParser {
+public:
+ MidiParser_SH();
+ ~MidiParser_SH();
+
+protected:
+ Common::Mutex _mutex;
+ void parseNextEvent(EventInfo &info);
+
+ uint8 _beats;
+ uint8 _lastEvent;
+ byte *_data;
+ byte *_trackEnd;
+
+public:
+ bool loadMusic(byte *musData, uint32 musSize);
+ virtual void unloadMusic();
+
+private:
+ byte *_musData;
+ uint32 _musDataSize;
+};
+
+class Music {
+private:
+ SherlockEngine *_vm;
+ Audio::Mixer *_mixer;
+ MidiParser *_midiParser;
+ MidiDriver *_midiDriver;
+ Audio::SoundHandle _digitalMusicHandle;
+ MusicType _musicType;
+
+ /**
+ * Play the specified music resource
+ */
+ bool playMusic(const Common::String &name);
+public:
+ bool _musicPlaying;
+ bool _musicOn;
+ int _musicVolume;
+ bool _midiOption;
+ Common::String _currentSongName, _nextSongName;
+public:
+ Music(SherlockEngine *vm, Audio::Mixer *mixer);
+ ~Music();
+
+ /**
+ * Saves sound-related settings
+ */
+ void syncMusicSettings();
+
+ /**
+ * Load a specified song
+ */
+ bool loadSong(int songNumber);
+
+ /**
+ * Load a specified song
+ */
+ bool loadSong(const Common::String &songName);
+
+ /**
+ * Start playing a song
+ */
+ void startSong();
+
+ /**
+ * Free any currently loaded song
+ */
+ void freeSong();
+
+ /**
+ * Stop playing the music
+ */
+ void stopMusic();
+
+ void waitTimerRoland(uint time);
+
+ bool isPlaying();
+ uint32 getCurrentPosition();
+
+ bool waitUntilMSec(uint32 msecTarget, uint32 maxMSec, uint32 additionalDelay, uint32 noMusicDelay);
+
+ /**
+ * Sets the volume of the MIDI music with a value ranging from 0 to 127
+ */
+ void setMIDIVolume(int volume);
+};
+
+} // End of namespace Sherlock
+
+#endif
+
diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp
new file mode 100644
index 0000000000..6ef08c28cc
--- /dev/null
+++ b/engines/sherlock/objects.cpp
@@ -0,0 +1,1533 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/util.h"
+#include "sherlock/objects.h"
+#include "sherlock/people.h"
+#include "sherlock/scene.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+#define START_FRAME 0
+
+#define NUM_ADJUSTED_WALKS 21
+
+// Distance to walk around WALK_AROUND boxes
+#define CLEAR_DIST_X 5
+#define CLEAR_DIST_Y 0
+
+#define ADJUST_COORD(COORD) \
+ if (COORD.x != -1) \
+ COORD.x *= FIXED_INT_MULTIPLIER; \
+ if (COORD.y != -1) \
+ COORD.y *= FIXED_INT_MULTIPLIER
+
+SherlockEngine *BaseObject::_vm;
+bool BaseObject::_countCAnimFrames;
+
+/*----------------------------------------------------------------*/
+
+void BaseObject::setVm(SherlockEngine *vm) {
+ _vm = vm;
+ _countCAnimFrames = false;
+}
+
+BaseObject::BaseObject() {
+ _type = INVALID;
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+ _walkCount = 0;
+ _allow = 0;
+ _frameNumber = 0;
+ _lookFlag = 0;
+ _requiredFlag[0] = _requiredFlag[1] = 0;
+ _status = 0;
+ _misc = 0;
+ _maxFrames = 0;
+ _flags = 0;
+ _aType = OBJECT;
+ _lookFrames = 0;
+ _seqCounter = 0;
+ _lookcAnim = 0;
+ _seqStack = 0;
+ _seqTo = 0;
+ _descOffset = 0;
+ _seqCounter2 = 0;
+ _seqSize = 0;
+ _quickDraw = 0;
+ _scaleVal = 0;
+ _gotoSeq = 0;
+ _talkSeq = 0;
+ _restoreSlot = 0;
+}
+
+bool BaseObject::hasAborts() const {
+ int seqNum = _talkSeq;
+
+ // See if the object is in it's regular sequence
+ bool startChecking = !seqNum || _type == CHARACTER;
+
+ uint idx = 0;
+ do
+ {
+ // Get the Frame value
+ int v = _sequences[idx++];
+
+ // See if we found an Allow Talk Interrupt Code
+ if (startChecking && v == ALLOW_TALK_CODE)
+ return true;
+
+ // If we've started checking and we've encountered another Talk or Listen Sequence Code,
+ // then we're done checking this sequence because this is where it would repeat
+ if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE))
+ return false;
+
+ // See if we've found the beginning of a Talk Sequence
+ if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) {
+ // If checking was already on and we came across one of these codes, then there couldn't
+ // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done.
+ if (startChecking)
+ return false;
+
+ seqNum--;
+ // See if we're at the correct Talk Sequence Number
+ if (!(seqNum & 127))
+ {
+ // Correct Sequence, Start Checking Now
+ startChecking = true;
+ }
+ } else {
+ // Move ahead any extra because of special control codes
+ switch (v) {
+ case 0: idx++; break;
+ case MOVE_CODE:
+ case TELEPORT_CODE: idx += 4; break;
+ case CALL_TALK_CODE:idx += 8; break;
+ case HIDE_CODE: idx += 2; break;
+ }
+ }
+ } while (idx < _seqSize);
+
+ return true;
+}
+
+void BaseObject::checkObject() {
+ Scene &scene = *_vm->_scene;
+ Sound &sound = *_vm->_sound;
+ Talk &talk = *_vm->_talk;
+ int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
+ bool codeFound;
+
+ if (_seqTo) {
+ byte *ptr = &_sequences[_frameNumber];
+ if (*ptr == _seqTo) {
+ // The sequence is completed. Reset to normal
+ *ptr = _seqTo + (IS_ROSE_TATTOO ? 0 : SEQ_TO_CODE + 128);
+ _seqTo = 0;
+ } else {
+ // Continue doing sequence
+ if (*ptr > _seqTo)
+ *ptr -= 1;
+ else
+ *ptr += 1;
+
+ return;
+ }
+ }
+
+ ++_frameNumber;
+
+ do {
+ if (!_sequences) {
+ warning("checkObject: _sequences is not set");
+ break;
+ }
+
+ // Check for end of sequence
+ codeFound = checkEndOfSequence();
+
+ if (_sequences[_frameNumber] >= 128 && _frameNumber < checkFrame) {
+ codeFound = true;
+ int v = _sequences[_frameNumber];
+
+ // Check for a Talk or Listen Sequence
+ if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) {
+ if (_gotoSeq) {
+ setObjTalkSequence(_gotoSeq);
+ } else {
+ ++_frameNumber;
+ }
+ } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) {
+ if (_talkSeq)
+ setObjTalkSequence(_talkSeq);
+ else
+ setObjSequence(0, false);
+ } else if (v >= GOTO_CODE) {
+ // Goto code found
+ v -= GOTO_CODE;
+ _seqCounter2 = _seqCounter;
+ _seqStack = _frameNumber + 1;
+ setObjSequence(v, false);
+ } else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) {
+ codeFound = true;
+ ++_frameNumber;
+ v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0);
+
+ if (sound._soundOn && !_countCAnimFrames) {
+ if (!scene._sounds[v]._name.empty() && sound._digitized)
+ sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY);
+ }
+ } else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) {
+ // Flip code
+ codeFound = true;
+ ++_frameNumber;
+ v -= FLIP_CODE;
+
+ // Alter the flipped status
+ switch (v) {
+ case 0:
+ // Clear the flag
+ _flags &= ~OBJ_FLIPPED;
+ break;
+ case 1:
+ // Set the flag
+ _flags |= OBJ_FLIPPED;
+ break;
+ case 2:
+ // Toggle the flag
+ _flags ^= OBJ_FLIPPED;
+ break;
+ default:
+ break;
+ }
+ } else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) {
+ _position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]);
+ _position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]);
+
+ _frameNumber += 5;
+ } else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) {
+ Common::String filename;
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_sequences[_frameNumber + 1 + idx] != 1)
+ filename += (char)_sequences[_frameNumber + 1 + idx];
+ else
+ break;
+ }
+
+ _frameNumber += 8;
+ talk.talkTo(filename);
+
+ } else if (IS_ROSE_TATTOO && v == HIDE_CODE) {
+ switch (_sequences[_frameNumber + 2]) {
+ case 1:
+ // Hide Object
+ if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN)
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ case 2:
+ // Activate Object
+ if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN)
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ case 3:
+ // Toggle Object
+ scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
+ break;
+
+ default:
+ break;
+ }
+ _frameNumber += 3;
+
+ } else {
+ v -= 128;
+
+ // 68-99 is a sequence code
+ if (v > SEQ_TO_CODE) {
+ if (IS_ROSE_TATTOO) {
+ ++_frameNumber;
+ byte *p = &_sequences[_frameNumber];
+ _seqTo = *p;
+ *p = *(p - 2);
+
+ if (*p > _seqTo)
+ *p -= 1;
+ else
+ *p += 1;
+
+ --_frameNumber;
+ } else {
+ byte *p = &_sequences[_frameNumber];
+ v -= SEQ_TO_CODE; // # from 1-32
+ _seqTo = v;
+ *p = *(p - 1);
+
+ if (*p > 128)
+ // If the high bit is set, convert to a real frame
+ *p -= (byte)(SEQ_TO_CODE - 128);
+
+ if (*p > _seqTo)
+ *p -= 1;
+ else
+ *p += 1;
+
+ // Will be incremented below to return back to original value
+ --_frameNumber;
+ v = 0;
+ }
+ } else if (IS_ROSE_TATTOO && v == 10) {
+ // Set delta for objects
+ _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]),
+ READ_LE_UINT16(&_sequences[_frameNumber + 3]));
+ _noShapeSize = Common::Point(0, 0);
+ _frameNumber += 4;
+
+ } else if (v == 10) {
+ // Set delta for objects
+ Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]);
+ if (pt.x > 128)
+ pt.x = (pt.x - 128) * -1;
+ else
+ pt.x--;
+
+ if (pt.y > 128)
+ pt.y = (pt.y - 128) * -1;
+ else
+ pt.y--;
+
+ _delta = pt;
+ _frameNumber += 2;
+
+ } else if (v < USE_COUNT) {
+ for (int idx = 0; idx < NAMES_COUNT; ++idx) {
+ checkNameForCodes(_use[v]._names[idx]);
+ }
+
+ if (_use[v]._useFlag)
+ _vm->setFlags(_use[v]._useFlag);
+ }
+
+ ++_frameNumber;
+ }
+ }
+ } while (codeFound);
+}
+
+bool BaseObject::checkEndOfSequence() {
+ Screen &screen = *_vm->_screen;
+ int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
+ bool result = false;
+
+ if (_type == REMOVE || _type == INVALID)
+ return false;
+
+ if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) {
+ result = true;
+
+ if (_frameNumber >= (checkFrame - 1)) {
+ _frameNumber = START_FRAME;
+ } else {
+ // Determine next sequence to use
+ int seq = _sequences[_frameNumber + 1];
+
+ if (seq == 99) {
+ --_frameNumber;
+ screen._backBuffer1.transBlitFrom(*_imageFrame, _position);
+ screen._backBuffer2.transBlitFrom(*_imageFrame, _position);
+ _type = INVALID;
+ } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) {
+ setObjTalkSequence(_talkSeq);
+ } else {
+ setObjSequence(seq, false);
+ }
+ }
+
+ if (_allow && _frameNumber == 0) {
+ // canimation just ended
+ if (_type != NO_SHAPE && _type != REMOVE) {
+ _type = REMOVE;
+
+ if (!_countCAnimFrames) {
+ // Save details before shape is removed
+ _delta.x = _imageFrame->_frame.w;
+ _delta.y = _imageFrame->_frame.h;
+ _position += _imageFrame->_offset;
+
+ // Free the images
+ delete _images;
+ _images = nullptr;
+ _imageFrame = nullptr;
+ }
+ } else {
+ _type = INVALID;
+ }
+ }
+ }
+
+ return result;
+}
+
+void BaseObject::setObjSequence(int seq, bool wait) {
+ Scene &scene = *_vm->_scene;
+ int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
+
+ if (seq >= 128) {
+ // Loop the sequence until the count exceeded
+ seq -= 128;
+
+ ++_seqCounter;
+ if (_seqCounter >= seq) {
+ // Go to next sequence
+ if (_seqStack) {
+ _frameNumber = _seqStack;
+ _seqStack = 0;
+ _seqCounter = _seqCounter2;
+ _seqCounter2 = 0;
+ if (_frameNumber >= checkFrame)
+ _frameNumber = START_FRAME;
+
+ return;
+ }
+
+ _frameNumber += 2;
+ if (_frameNumber >= checkFrame)
+ _frameNumber = 0;
+
+ _seqCounter = 0;
+ if (_sequences[_frameNumber] == 0)
+ seq = _sequences[_frameNumber + 1];
+ else
+ return;
+ } else {
+ // Find beginning of sequence
+ do {
+ --_frameNumber;
+ } while (_frameNumber > 0 && _sequences[_frameNumber] != 0);
+
+ if (_frameNumber != 0)
+ _frameNumber += 2;
+
+ return;
+ }
+ } else {
+ // Reset sequence counter
+ _seqCounter = 0;
+ }
+
+ int idx = 0;
+ int seqCc = 0;
+
+ while (seqCc < seq && idx < checkFrame) {
+ ++idx;
+ if (_sequences[idx] == 0) {
+ ++seqCc;
+ idx += 2;
+ }
+ }
+
+ if (idx >= checkFrame)
+ idx = 0;
+ _frameNumber = idx;
+
+ if (wait) {
+ seqCc = idx;
+ while (_sequences[idx] != 0)
+ ++idx;
+
+ idx = idx - seqCc + 2;
+ for (; idx > 0; --idx)
+ scene.doBgAnim();
+ }
+}
+
+int BaseObject::checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId) {
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ UserInterface &ui = *_vm->_ui;
+ bool printed = false;
+
+ scene.toggleObject(name);
+
+ if (name.hasPrefix("*")) {
+ // A code was found
+ printed = true;
+ char ch = (name == "*") ? 0 : toupper(name[1]);
+
+ switch (ch) {
+ case 'C':
+ talk.talkTo(name.c_str() + 2);
+ break;
+
+ case 'T':
+ case 'B':
+ case 'F':
+ case 'W':
+ // Nothing: action was already done before canimation
+ break;
+
+ case 'G':
+ case 'A': {
+ // G: Have object go somewhere
+ // A: Add onto existing co-ordinates
+ Common::String sx(name.c_str() + 2, name.c_str() + 5);
+ Common::String sy(name.c_str() + 6, name.c_str() + 9);
+
+ if (ch == 'G')
+ _position = Common::Point(atoi(sx.c_str()), atoi(sy.c_str()));
+ else
+ _position += Common::Point(atoi(sx.c_str()), atoi(sy.c_str()));
+ break;
+ }
+
+ case 'V':
+ // Do nothing for Verb codes. This is only a flag for Inventory syntax
+ break;
+
+ default:
+ if (ch >= '0' && ch <= '9') {
+ scene._goToScene = atoi(name.c_str() + 1);
+
+ if (IS_SERRATED_SCALPEL && scene._goToScene < 97) {
+ Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map;
+ if (map[scene._goToScene].x) {
+ map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
+ }
+ }
+
+ const char *p;
+ if ((p = strchr(name.c_str(), ',')) != nullptr) {
+ ++p;
+
+ Common::String s(p, p + 3);
+ people._savedPos.x = atoi(s.c_str());
+
+ s = Common::String(p + 3, p + 6);
+ people._savedPos.y = atoi(s.c_str());
+
+ s = Common::String(p + 6, p + 9);
+ people._savedPos._facing = atoi(s.c_str());
+ if (people._savedPos._facing == 0)
+ people._savedPos._facing = 10;
+ } else if ((p = strchr(name.c_str(), '/')) != nullptr) {
+ people._savedPos = PositionFacing(1, 0, 100 + atoi(p + 1));
+ }
+ } else {
+ scene._goToScene = 100;
+ }
+
+ people[HOLMES]._position = Point32(0, 0);
+ break;
+ }
+ } else if (name.hasPrefix("!")) {
+ // Message attached to canimation
+ int messageNum = atoi(name.c_str() + 1);
+ ui._infoFlag = true;
+ ui.clearInfo();
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, messageNum);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
+ ui._menuCounter = 25;
+ } else if (name.hasPrefix("@")) {
+ // Message attached to canimation
+ ui._infoFlag = true;
+ ui.clearInfo();
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", name.c_str() + 1);
+ printed = true;
+ ui._menuCounter = 25;
+ }
+
+ return printed;
+}
+
+/*----------------------------------------------------------------*/
+
+void Sprite::clear() {
+ _name = "";
+ _description = "";
+ _examine.clear();
+ _pickUp = "";
+ _walkSequences.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+ _walkCount = 0;
+ _allow = 0;
+ _frameNumber = _sequenceNumber = 0;
+ _position.x = _position.y = 0;
+ _delta.x = _delta.y = 0;
+ _oldPosition.x = _oldPosition.y = 0;
+ _oldSize.x = _oldSize.y = 0;
+ _goto.x = _goto.y = 0;
+ _type = INVALID;
+ _pickUp.clear();
+ _noShapeSize.x = _noShapeSize.y = 0;
+ _status = 0;
+ _misc = 0;
+ _altImages = nullptr;
+ _altSeq = 0;
+ Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr);
+}
+
+void Sprite::setImageFrame() {
+ int frameNum = MAX(_frameNumber, 0);
+ int imageNumber = _walkSequences[_sequenceNumber][frameNum];
+
+ if (IS_SERRATED_SCALPEL)
+ imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2;
+ else if (imageNumber > _maxFrames)
+ imageNumber = 1;
+
+ // Get the images to use
+ ImageFile *images = _altSeq ? _altImages : _images;
+ assert(images);
+
+ if (IS_3DO) {
+ // only do this to the image-array with 110 entries
+ // map uses another image-array and this code
+ if (images->size() == 110) {
+ // 3DO has 110 animation frames inside walk.anim
+ // PC has 55
+ // this adjusts the framenumber accordingly
+ // sort of HACK
+ imageNumber *= 2;
+ }
+ } else if (IS_ROSE_TATTOO) {
+ --imageNumber;
+ }
+
+ // Set the frame pointer
+ _imageFrame = &(*images)[imageNumber];
+}
+
+void Sprite::checkSprite() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Point32 pt;
+ Common::Rect objBounds;
+ Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER);
+
+ if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter))
+ return;
+
+ pt = _walkCount ? _position + _delta : _position;
+ pt.x /= FIXED_INT_MULTIPLIER;
+ pt.y /= FIXED_INT_MULTIPLIER;
+
+ if (IS_ROSE_TATTOO) {
+ checkObject();
+
+ // For Rose Tattoo, we only do the further processing for Sherlock
+ if (this != &people[HOLMES])
+ return;
+ }
+
+ for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) {
+ Object &obj = scene._bgShapes[idx];
+ if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN)
+ continue;
+
+ if (obj._type == NO_SHAPE) {
+ objBounds = Common::Rect(obj._position.x, obj._position.y,
+ obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1);
+ } else {
+ int xp = obj._position.x + obj._imageFrame->_offset.x;
+ int yp = obj._position.y + obj._imageFrame->_offset.y;
+ objBounds = Common::Rect(xp, yp,
+ xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1);
+ }
+
+ if (objBounds.contains(pt)) {
+ if (objBounds.contains(spritePt)) {
+ // Current point is already inside the the bounds, so impact occurred
+ // on a previous call. So simply do nothing until we're clear of the box
+ switch (obj._aType) {
+ case TALK_MOVE:
+ if (_walkCount) {
+ // Holmes is moving
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ }
+ break;
+
+ case PAL_CHANGE:
+ case PAL_CHANGE2:
+ if (_walkCount) {
+ int palStart = atoi(obj._use[0]._names[0].c_str()) * 3;
+ int palLength = atoi(obj._use[0]._names[1].c_str()) * 3;
+ int templ = atoi(obj._use[0]._names[2].c_str()) * 3;
+ if (templ == 0)
+ templ = 100;
+
+ // Ensure only valid palette change data found
+ if (palLength > 0) {
+ // Figure out how far into the shape Holmes is so that we
+ // can figure out what percentage of the original palette
+ // to set the current palette to
+ int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width();
+ palPercent = palPercent * templ / 100;
+ if (obj._aType == PAL_CHANGE)
+ // Invert percentage
+ palPercent = 100 - palPercent;
+
+ for (int i = palStart; i < (palStart + palLength); ++i)
+ screen._sMap[i] = screen._cMap[i] * palPercent / 100;
+
+ events.pollEvents();
+ screen.setPalette(screen._sMap);
+ }
+ }
+ break;
+
+ case TALK:
+ case TALK_EVERY:
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ // New impact just occurred
+ switch (obj._aType) {
+ case BLANK_ZONE:
+ // A blank zone masks out all other remaining zones underneath it.
+ // If this zone is hit, exit the outer loop so we do not check anymore
+ return;
+
+ case SOLID:
+ case TALK:
+ // Stop walking
+ if (obj._aType == TALK) {
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ } else {
+ gotoStand();
+ }
+ break;
+
+ case TALK_EVERY:
+ if (obj._aType == TALK_EVERY) {
+ obj._type = HIDDEN;
+ obj.setFlagsAndToggles();
+ talk.talkTo(obj._use[0]._target);
+ } else {
+ gotoStand();
+ }
+ break;
+
+ case FLAG_SET:
+ obj.setFlagsAndToggles();
+ obj._type = HIDDEN;
+ break;
+
+ case WALK_AROUND:
+ if (objBounds.contains(people[HOLMES]._walkTo.front())) {
+ // Reached zone
+ gotoStand();
+ } else {
+ // Destination not within box, walk to best corner
+ Common::Point walkPos;
+
+ if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) {
+ // Impact occurred due to vertical movement. Determine whether to
+ // travel to the left or right side
+ if (_delta.x > 0)
+ // Go to right side
+ walkPos.x = objBounds.right + CLEAR_DIST_X;
+ else if (_delta.x < 0) {
+ // Go to left side
+ walkPos.x = objBounds.left - CLEAR_DIST_X;
+ } else {
+ // Going straight up or down. So choose best side
+ if (spritePt.x >= (objBounds.left + objBounds.width() / 2))
+ walkPos.x = objBounds.right + CLEAR_DIST_X;
+ else
+ walkPos.x = objBounds.left - CLEAR_DIST_X;
+ }
+
+ walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y :
+ objBounds.bottom + CLEAR_DIST_Y;
+ } else {
+ // Impact occurred due to horizontal movement
+ if (_delta.y > 0)
+ // Go to bottom of box
+ walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
+ else if (_delta.y < 0)
+ // Go to top of box
+ walkPos.y = objBounds.top - CLEAR_DIST_Y;
+ else {
+ // Going straight horizontal, so choose best side
+ if (spritePt.y >= (objBounds.top + objBounds.height() / 2))
+ walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
+ else
+ walkPos.y = objBounds.top - CLEAR_DIST_Y;
+ }
+
+ walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X :
+ objBounds.right + CLEAR_DIST_X;
+ }
+
+ walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2;
+ people[HOLMES]._walkDest = walkPos;
+ people[HOLMES]._walkTo.push(walkPos);
+ people[HOLMES].setWalking();
+ }
+ break;
+
+ case DELTA:
+ _position.x += 200;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+const Common::Rect Sprite::getOldBounds() const {
+ return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y);
+}
+
+/*----------------------------------------------------------------*/
+
+void WalkSequence::load(Common::SeekableReadStream &s) {
+ char buffer[9];
+ s.read(buffer, 9);
+ _vgsName = Common::String(buffer);
+ _horizFlip = s.readByte() != 0;
+
+ _sequences.resize(s.readUint16LE());
+ s.skip(4); // Skip over pointer field of structure
+
+ s.read(&_sequences[0], _sequences.size());
+}
+
+/*----------------------------------------------------------------*/
+
+WalkSequences &WalkSequences::operator=(const WalkSequences &src) {
+ resize(src.size());
+ for (uint idx = 0; idx < size(); ++idx) {
+ const WalkSequence &wSrc = src[idx];
+ WalkSequence &wDest = (*this)[idx];
+ wDest._horizFlip = wSrc._horizFlip;
+
+ wDest._sequences.resize(wSrc._sequences.size());
+ Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]);
+ }
+
+ return *this;
+}
+
+/*----------------------------------------------------------------*/
+
+ActionType::ActionType() {
+ _cAnimNum = _cAnimSpeed = 0;
+ _useFlag = 0;
+}
+
+void ActionType::load(Common::SeekableReadStream &s) {
+ char buffer[12];
+
+ _cAnimNum = s.readByte();
+ _cAnimSpeed = s.readByte();
+ if (_cAnimSpeed & 0x80)
+ _cAnimSpeed = -(_cAnimSpeed & 0x7f);
+
+ for (int idx = 0; idx < NAMES_COUNT; ++idx) {
+ s.read(buffer, 12);
+ _names[idx] = Common::String(buffer);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+UseType::UseType(): ActionType() {
+}
+
+void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ char buffer[12];
+
+ if (isRoseTattoo) {
+ s.read(buffer, 12);
+ _verb = Common::String(buffer);
+ }
+
+ ActionType::load(s);
+
+ _useFlag = s.readSint16LE();
+
+ if (!isRoseTattoo)
+ s.skip(6);
+
+ s.read(buffer, 12);
+ _target = Common::String(buffer);
+}
+
+void UseType::load3DO(Common::SeekableReadStream &s) {
+ char buffer[12];
+
+ _cAnimNum = s.readByte();
+ _cAnimSpeed = s.readByte();
+ if (_cAnimSpeed & 0x80)
+ _cAnimSpeed = -(_cAnimSpeed & 0x7f);
+
+ for (int idx = 0; idx < NAMES_COUNT; ++idx) {
+ s.read(buffer, 12);
+ _names[idx] = Common::String(buffer);
+ }
+
+ _useFlag = s.readSint16BE();
+
+ s.skip(6);
+
+ s.read(buffer, 12);
+ _target = Common::String(buffer);
+}
+
+void UseType::synchronize(Serializer &s) {
+ s.syncString(_verb);
+ s.syncAsSint16LE(_cAnimNum);
+ s.syncAsSint16LE(_cAnimSpeed);
+ s.syncAsSint16LE(_useFlag);
+
+ for (int idx = 0; idx < 4; ++idx)
+ s.syncString(_names[idx]);
+ s.syncString(_target);
+}
+
+/*----------------------------------------------------------------*/
+
+Object::Object(): BaseObject() {
+ _sequenceNumber = 0;
+ _sequenceOffset = 0;
+ _pickup = 0;
+ _defaultCommand = 0;
+ _pickupFlag = 0;
+}
+
+void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ char buffer[41];
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+ s.read(buffer, 41);
+ _description = Common::String(buffer);
+
+ _examine.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+
+ s.skip(4);
+ _sequenceOffset = s.readUint16LE();
+ s.seek(10, SEEK_CUR);
+
+ _walkCount = s.readByte();
+ _allow = s.readByte();
+ _frameNumber = s.readSint16LE();
+ _sequenceNumber = s.readSint16LE();
+ _position.x = s.readSint16LE();
+ _position.y = s.readSint16LE();
+ _delta.x = s.readSint16LE();
+ _delta.y = s.readSint16LE();
+ _type = (SpriteType)s.readUint16LE();
+ _oldPosition.x = s.readSint16LE();
+ _oldPosition.y = s.readSint16LE();
+ _oldSize.x = s.readUint16LE();
+ _oldSize.y = s.readUint16LE();
+
+ _goto.x = s.readSint16LE();
+ _goto.y = s.readSint16LE();
+ if (!isRoseTattoo) {
+ _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
+ _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
+ }
+
+ _pickup = isRoseTattoo ? 0 : s.readByte();
+ _defaultCommand = isRoseTattoo ? 0 : s.readByte();
+ _lookFlag = s.readSint16LE();
+ _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE();
+ _requiredFlag[0] = s.readSint16LE();
+ _noShapeSize.x = s.readUint16LE();
+ _noShapeSize.y = s.readUint16LE();
+ _status = s.readUint16LE();
+ _misc = s.readByte();
+ _maxFrames = s.readUint16LE();
+ _flags = s.readByte();
+
+ if (!isRoseTattoo)
+ _aOpen.load(s);
+
+ _aType = (AType)s.readByte();
+ _lookFrames = s.readByte();
+ _seqCounter = s.readByte();
+ if (isRoseTattoo) {
+ _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER;
+ _lookPosition.y = s.readSint16LE() * FIXED_INT_MULTIPLIER;
+ } else {
+ _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100;
+ _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER;
+ }
+ _lookPosition._facing = s.readByte();
+ _lookcAnim = s.readByte();
+
+ if (!isRoseTattoo)
+ _aClose.load(s);
+
+ _seqStack = s.readByte();
+ _seqTo = s.readByte();
+ _descOffset = s.readUint16LE();
+ _seqCounter2 = s.readByte();
+ _seqSize = s.readUint16LE();
+
+ if (isRoseTattoo) {
+ for (int idx = 0; idx < 6; ++idx)
+ _use[idx].load(s, true);
+
+ _quickDraw = s.readByte();
+ _scaleVal = s.readUint16LE();
+ _requiredFlag[1] = s.readSint16LE();
+ _gotoSeq = s.readByte();
+ _talkSeq = s.readByte();
+ _restoreSlot = s.readByte();
+ } else {
+ s.skip(1);
+ _aMove.load(s);
+ s.skip(8);
+
+ for (int idx = 0; idx < 4; ++idx)
+ _use[idx].load(s, false);
+ }
+ //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum);
+}
+
+void Object::load3DO(Common::SeekableReadStream &s) {
+ int32 streamStartPos = s.pos();
+ char buffer[41];
+
+ _examine.clear();
+ _sequences = nullptr;
+ _images = nullptr;
+ _imageFrame = nullptr;
+
+ // on 3DO all of this data is reordered!!!
+ // it seems that possibly the 3DO compiler reordered the global struct
+ // 3DO size for 1 object is 588 bytes
+ s.skip(4);
+ _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE
+ s.seek(10, SEEK_CUR);
+
+ // Offset 16
+ _frameNumber = s.readSint16BE();
+ _sequenceNumber = s.readSint16BE();
+ _position.x = s.readSint16BE();
+ _position.y = s.readSint16BE();
+ _delta.x = s.readSint16BE();
+ _delta.y = s.readSint16BE();
+ _type = (SpriteType)s.readUint16BE();
+ _oldPosition.x = s.readSint16BE();
+ _oldPosition.y = s.readSint16BE();
+ _oldSize.x = s.readUint16BE();
+ _oldSize.y = s.readUint16BE();
+
+ _goto.x = s.readSint16BE();
+ _goto.y = s.readSint16BE();
+ _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
+ _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
+
+ // Offset 42
+ warning("pos %d", s.pos());
+
+ // Unverified
+ _lookFlag = s.readSint16BE();
+ _pickupFlag = s.readSint16BE();
+ _requiredFlag[0] = s.readSint16BE();
+ _noShapeSize.x = s.readUint16BE();
+ _noShapeSize.y = s.readUint16BE();
+ _status = s.readUint16BE();
+ // Unverified END
+
+ _maxFrames = s.readUint16BE();
+ // offset 56
+ _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100;
+ // offset 58
+ _descOffset = s.readUint16BE();
+ _seqSize = s.readUint16BE();
+
+ s.skip(2); // boundary filler
+
+ // 288 bytes
+ for (int idx = 0; idx < 4; ++idx) {
+ _use[idx].load3DO(s);
+ s.skip(2); // Filler
+ }
+
+ // 158 bytes
+ _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes
+ s.skip(2); // Boundary filler
+ _aClose.load(s);
+ s.skip(2); // Filler
+ _aMove.load(s);
+ s.skip(2); // Filler
+
+ // offset 508
+ // 3DO: name is at the end
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+ s.read(buffer, 41);
+ _description = Common::String(buffer);
+
+ // Unverified
+ _walkCount = s.readByte();
+ _allow = s.readByte();
+ _pickup = s.readByte();
+ _defaultCommand = s.readByte();
+ // Unverified END
+
+ // Probably those here?!?!
+ _misc = s.readByte();
+ _flags = s.readByte();
+
+ // Unverified
+ _aType = (AType)s.readByte();
+ _lookFrames = s.readByte();
+ _seqCounter = s.readByte();
+ // Unverified END
+
+ _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER;
+ _lookPosition._facing = s.readByte();
+
+ // Unverified
+ _lookcAnim = s.readByte();
+ _seqStack = s.readByte();
+ _seqTo = s.readByte();
+ _seqCounter2 = s.readByte();
+ // Unverified END
+
+ s.skip(12); // Unknown
+
+ //warning("object %s, offset %d", _name.c_str(), streamPos);
+ //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y);
+ //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand);
+ int32 dataSize = s.pos() - streamStartPos;
+ assert(dataSize == 588);
+}
+
+void Object::toggleHidden() {
+ if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) {
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
+ _seqTo = 0;
+
+ if (_images == nullptr || _images->size() == 0)
+ // No shape to erase, so flag as hidden
+ _type = HIDDEN;
+ else
+ // Otherwise, flag it to be hidden after it gets erased
+ _type = HIDE_SHAPE;
+ } else if (_type != INVALID) {
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
+ _seqTo = 0;
+
+ _seqCounter = _seqCounter2 = 0;
+ _seqStack = 0;
+ _frameNumber = -1;
+
+ if (_images == nullptr || _images->size() == 0) {
+ _type = NO_SHAPE;
+ } else {
+ _type = ACTIVE_BG_SHAPE;
+ int idx = _sequences[0];
+ if (idx >= _maxFrames)
+ // Turn on: set up first frame
+ idx = 0;
+
+ _imageFrame = &(*_images)[idx];
+ }
+ }
+}
+
+void Object::setObjTalkSequence(int seq) {
+ Talk &talk = *_vm->_talk;
+
+ // See if we're supposed to restore the object's sequence from the talk sequence stack
+ if (seq == -1) {
+ TalkSequence &ts = talk._talkSequenceStack[_restoreSlot];
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo;
+ _frameNumber = ts._frameNumber;
+ _sequenceNumber = ts._sequenceNumber;
+ _seqStack = ts._seqStack;
+ _seqTo = ts._seqTo;
+ _seqCounter = ts._seqCounter;
+ _seqCounter2 = ts._seqCounter2;
+ _talkSeq = 0;
+
+ // Flag this slot as free again
+ ts._obj = nullptr;
+
+ return;
+ }
+
+ assert(_type != CHARACTER);
+
+ talk.pushTalkSequence(this);
+ int talkSeqNum = seq;
+
+ // Find where the talk sequence data begins in the object
+ int idx = 0;
+ for (;;) {
+ // Get the Frame value
+ byte f = _sequences[idx++];
+
+ // See if we've found the beginning of a Talk Sequence
+ if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) {
+ --seq;
+
+ // See if we're at the correct Talk Sequence Number
+ if (!(seq & 127))
+ {
+ // Correct Sequence, Start Talking Here
+ if (_seqTo != 0)
+ _sequences[_frameNumber] = _seqTo;
+ _frameNumber = idx;
+ _seqTo = 0;
+ _seqStack = 0;
+ _seqCounter = 0;
+ _seqCounter2 = 0;
+ _talkSeq = talkSeqNum;
+ break;
+ }
+ } else {
+ // Move ahead any extra because of special control codes
+ switch (f) {
+ case 0: idx++; break;
+ case MOVE_CODE:
+ case TELEPORT_CODE: idx += 4; break;
+ case CALL_TALK_CODE: idx += 8; break;
+ case HIDE_CODE: idx += 2; break;
+ }
+ }
+
+ // See if we're out of sequence data
+ if (idx >= (int)_seqSize)
+ break;
+ }
+}
+
+void Object::setFlagsAndToggles() {
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ for (int useIdx = 0; useIdx < USE_COUNT; ++useIdx) {
+ if (_use[useIdx]._useFlag) {
+ if (!_vm->readFlags(_use[useIdx]._useFlag))
+ _vm->setFlags(_use[useIdx]._useFlag);
+ }
+
+ if (_use[useIdx]._cAnimSpeed) {
+ if (_use[useIdx]._cAnimNum == 0)
+ // 0 is really a 10
+ scene.startCAnim(9, _use[useIdx]._cAnimSpeed);
+ else
+ scene.startCAnim(_use[useIdx]._cAnimNum - 1, _use[useIdx]._cAnimSpeed);
+ }
+
+ if (!talk._talkToAbort) {
+ for (int idx = 0; idx < NAMES_COUNT; ++idx)
+ scene.toggleObject(_use[useIdx]._names[idx]);
+ }
+ }
+}
+
+void Object::adjustObject() {
+ if (_type == REMOVE)
+ return;
+
+ if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) {
+ // The shape position is in pixels, and the delta is in fixed integer amounts
+ int t;
+ _noShapeSize.x += _delta.x;
+ t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10);
+ _noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10);
+ _position.x += t;
+
+ _noShapeSize.y += _delta.y;
+ t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10);
+ _noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10);
+ _position.y += t;
+ } else if (IS_SERRATED_SCALPEL) {
+ // The delta is in whole pixels, so simply adjust the position with it
+ _position += _delta;
+ }
+
+ if (_position.y > LOWER_LIMIT)
+ _position.y = LOWER_LIMIT;
+
+ if (_type != NO_SHAPE) {
+ int frame = _frameNumber;
+ if (frame == -1)
+ frame = 0;
+
+ int imgNum = _sequences[frame];
+ if (imgNum > _maxFrames)
+ imgNum = 1;
+
+ _imageFrame = &(*_images)[imgNum - 1];
+ }
+}
+
+int Object::pickUpObject(FixedTextActionId fixedTextActionId) {
+ FixedText &fixedText = *_vm->_fixedText;
+ Inventory &inv = *_vm->_inventory;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ UserInterface &ui = *_vm->_ui;
+ int pickup = _pickup & 0x7f;
+ bool printed = false;
+ int numObjects = 0;
+
+ if (pickup == 99) {
+ for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
+ if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+
+ return 0;
+ }
+
+ if (!pickup || (pickup > 50 && pickup <= 80)) {
+ int message = _pickup;
+ if (message > 50)
+ message -= 50;
+
+ ui._infoFlag = true;
+ ui.clearInfo();
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, message);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
+ ui._menuCounter = 30;
+ } else {
+ // Pick it up
+ bool takeFlag = true;
+ if ((_pickup & 0x80) == 0) {
+ // Play an animation
+ if (pickup > 80) {
+ takeFlag = false; // Don't pick it up
+ scene.startCAnim(pickup - 81, 1);
+ if (_pickupFlag)
+ _vm->setFlags(_pickupFlag);
+ } else {
+ scene.startCAnim(pickup - 1, 1);
+ if (!talk._talkToAbort) {
+ // Erase the shape
+ _type = _type == NO_SHAPE ? INVALID : REMOVE;
+ }
+ }
+
+ if (talk._talkToAbort)
+ return 0;
+ } else {
+ // Play generic pickup sequence
+ // Original moved cursor position here
+ people[HOLMES].goAllTheWay();
+ ui._menuCounter = 25;
+ ui._temp1 = 1;
+ }
+
+ for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
+ if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+ if (talk._talkToAbort)
+ return 0;
+
+ // Add the item to the player's inventory
+ if (takeFlag)
+ numObjects = inv.putItemInInventory(*this);
+
+ if (!printed) {
+ ui._infoFlag = true;
+ ui.clearInfo();
+
+ Common::String itemName = _description;
+ itemName.setChar(tolower(itemName[0]), 0);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Picked up %s", itemName.c_str());
+ ui._menuCounter = 25;
+ }
+ }
+
+ return numObjects;
+}
+
+const Common::Rect Object::getNewBounds() const {
+ Point32 pt = _position;
+ if (_imageFrame)
+ pt += _imageFrame->_offset;
+
+ return Common::Rect(pt.x, pt.y, pt.x + frameWidth(), pt.y + frameHeight());
+}
+
+const Common::Rect Object::getNoShapeBounds() const {
+ return Common::Rect(_position.x, _position.y,
+ _position.x + _noShapeSize.x, _position.y + _noShapeSize.y);
+}
+
+const Common::Rect Object::getOldBounds() const {
+ return Common::Rect(_oldPosition.x, _oldPosition.y,
+ _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y);
+}
+
+/*----------------------------------------------------------------*/
+
+void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset) {
+ char buffer[12];
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+
+ if (isRoseTattoo) {
+ Common::fill(&_sequences[0], &_sequences[30], 0);
+ _dataSize = s.readUint32LE();
+ } else {
+ s.read(_sequences, 30);
+ }
+
+ _position.x = s.readSint16LE();
+ _position.y = s.readSint16LE();
+
+ if (isRoseTattoo) {
+ _flags = s.readByte();
+ _scaleVal = s.readSint16LE();
+ } else {
+ _dataSize = s.readUint32LE();
+ _type = (SpriteType)s.readUint16LE();
+ _flags = s.readByte();
+ }
+
+ _goto[0].x = s.readSint16LE();
+ _goto[0].y = s.readSint16LE();
+ _goto[0]._facing = s.readSint16LE();
+ ADJUST_COORD(_goto[0]);
+
+ if (isRoseTattoo) {
+ // Get Goto position and facing for second NPC
+ _goto[1].x = s.readSint16LE();
+ _goto[1].y = s.readSint16LE();
+ _goto[1]._facing = s.readSint16LE();
+ ADJUST_COORD(_goto[1]);
+ } else if (_goto[0].x != -1) {
+ // For Serrated Scalpel, adjust the loaded co-ordinates
+ _goto[0].x = _goto[0].x / 100;
+ _goto[0].y = _goto[0].y / 100;
+ }
+
+ _teleport[0].x = s.readSint16LE();
+ _teleport[0].y = s.readSint16LE();
+ _teleport[0]._facing = s.readSint16LE();
+ ADJUST_COORD(_teleport[0]);
+
+ if (isRoseTattoo) {
+ // Get Teleport position and facing for second NPC
+ _teleport[1].x = s.readSint16LE();
+ _teleport[1].y = s.readSint16LE();
+ _teleport[1]._facing = s.readSint16LE();
+ ADJUST_COORD(_teleport[1]);
+ } else if (_teleport[0].x != -1) {
+ // For Serrated Scalpel, adjust the loaded co-ordinates
+ _teleport[0].x = _teleport[0].x / 100;
+ _teleport[0].y = _teleport[0].y / 100;
+ }
+
+ // Save offset of data, which is actually inside another table inside the room data file
+ // This table is at offset 44 for Serrated Scalpel
+ // TODO: find it for the other game
+ _dataOffset = dataOffset;
+}
+
+void CAnim::load3DO(Common::SeekableReadStream &s, uint32 dataOffset) {
+ // this got reordered on 3DO
+ // maybe it was the 3DO compiler
+
+ _dataSize = s.readUint32BE();
+ // Save offset of data, which is inside another table inside the room data file
+ _dataOffset = dataOffset;
+
+ _position.x = s.readSint16BE();
+ _position.y = s.readSint16BE();
+
+ _type = (SpriteType)s.readUint16BE();
+
+ _goto[0].x = s.readSint16BE();
+ _goto[0].y = s.readSint16BE();
+ _goto[0]._facing = s.readSint16BE();
+
+ _teleport[0].x = s.readSint16BE();
+ _teleport[0].y = s.readSint16BE();
+ _teleport[0]._facing = s.readSint16BE();
+
+ char buffer[12];
+ s.read(buffer, 12);
+ _name = Common::String(buffer);
+
+ s.read(_sequences, 30);
+ _flags = s.readByte();
+
+ s.skip(3); // Filler
+
+ _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100;
+ _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100;
+ _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100;
+ _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100;
+}
+
+/*----------------------------------------------------------------*/
+
+SceneImage::SceneImage() {
+ _images = nullptr;
+ _maxFrames = 0;
+ _filesize = 0;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/objects.h b/engines/sherlock/objects.h
new file mode 100644
index 0000000000..996054be43
--- /dev/null
+++ b/engines/sherlock/objects.h
@@ -0,0 +1,478 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_OBJECTS_H
+#define SHERLOCK_OBJECTS_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "common/str.h"
+#include "sherlock/image_file.h"
+#include "sherlock/fixed_text.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+enum ObjectAllow {
+ ALLOW_MOVE = 1, ALLOW_OPEN = 2, ALLOW_CLOSE = 4
+};
+
+enum SpriteType {
+ INVALID = 0,
+ CHARACTER = 1,
+ CURSOR = 2,
+ STATIC_BG_SHAPE = 3, // Background shape that doesn't animate
+ ACTIVE_BG_SHAPE = 4, // Background shape that animates
+ REMOVE = 5, // Object should be removed next frame
+ NO_SHAPE = 6, // Background object with no shape
+ HIDDEN = 7, // Hidden backgruond object
+ HIDE_SHAPE = 8, // Object needs to be hidden
+
+ // Rose Tattoo
+ HIDDEN_CHARACTER = 128
+};
+
+enum AType {
+ OBJECT = 0,
+ PERSON = 1,
+ SOLID = 2,
+ TALK = 3, // Standard talk zone
+ FLAG_SET = 4,
+ DELTA = 5,
+ WALK_AROUND = 6,
+ TALK_EVERY = 7, // Talk zone that turns on every room visit
+ TALK_MOVE = 8, // Talk zone that only activates when Holmes moves
+ PAL_CHANGE = 9, // Changes the palette down so that it gets darker
+ PAL_CHANGE2 = 10, // Same as PAL_CHANGE, except that it goes up
+ SCRIPT_ZONE = 11, // If this is clicked in, it is activated
+ BLANK_ZONE = 12, // This masks out other objects when entered
+ NOWALK_ZONE = 13 // Player cannot walk here
+};
+
+// Different levels for sprites to be at
+enum {
+ BEHIND = 0, NORMAL_BEHIND = 1, NORMAL_FORWARD = 2, FORWARD = 3
+};
+
+#define MAX_HOLMES_SEQUENCE 16
+#define MAX_FRAME 30
+#define FIXED_INT_MULTIPLIER 1000
+
+// code put into sequences to defines 1-10 type seqs
+#define SEQ_TO_CODE 67
+#define FLIP_CODE (64 + 128)
+#define SOUND_CODE (34 + 128)
+#define HIDE_CODE (7+128) // Code for hiding/unhiding an object from a Sequence
+#define CALL_TALK_CODE (8+128) // Code for call a Talk File from a Sequence
+#define TELEPORT_CODE (9+128) // Code for setting Teleport Data (X,Y)
+#define MOVE_CODE (10+128) // Code for setting Movement Delta (X,Y)
+
+#define GOTO_CODE 228
+#define TALK_SEQ_CODE 252 // Code specifying start of talk sequence frames in a Sequence
+#define TALK_LISTEN_CODE 251 // Code specifying start of talk listen frames in a Sequence
+#define ALLOW_TALK_CODE 250
+
+#define UPPER_LIMIT 0
+#define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT)
+#define LEFT_LIMIT 0
+#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH
+
+class Point32 {
+public:
+ int x;
+ int y;
+
+ Point32() : x(0), y(0) {}
+ Point32(int x1, int y1) : x(x1), y(y1) {}
+ Point32(const Common::Point &pt) : x(pt.x), y(pt.y) {}
+
+ bool operator==(const Point32 &p) const { return x == p.x && y == p.y; }
+ bool operator!=(const Point32 &p) const { return x != p.x || y != p.y; }
+ Point32 operator+(const Point32 &delta) const { return Point32(x + delta.x, y + delta.y); }
+ Point32 operator-(const Point32 &delta) const { return Point32(x - delta.x, y - delta.y); }
+ operator Common::Point() { return Common::Point(x, y); }
+
+ void operator+=(const Point32 &delta) { x += delta.x; y += delta.y; }
+ void operator-=(const Point32 &delta) { x -= delta.x; y -= delta.y; }
+};
+
+class PositionFacing : public Point32 {
+public:
+ int _facing;
+
+ PositionFacing() : Point32(), _facing(0) {}
+ PositionFacing(int xp, int yp, int theFacing) : Point32(xp, yp), _facing(theFacing) {}
+ PositionFacing &operator=(const Point32 &pt) {
+ x = pt.x; y = pt.y;
+ return *this;
+ }
+};
+
+struct WalkSequence {
+ Common::String _vgsName;
+ bool _horizFlip;
+ Common::Array<byte> _sequences;
+
+ WalkSequence() : _horizFlip(false) {}
+ const byte &operator[](int idx) { return _sequences[idx]; }
+
+ /**
+ * Load data for the sequence from a stream
+ */
+ void load(Common::SeekableReadStream &s);
+};
+
+class WalkSequences : public Common::Array < WalkSequence > {
+public:
+ WalkSequences &operator=(const WalkSequences &src);
+};
+
+enum { REVERSE_DIRECTION = 0x80 };
+#define NAMES_COUNT 4
+
+struct ActionType {
+ int _cAnimNum;
+ int _cAnimSpeed;
+ Common::String _names[NAMES_COUNT];
+ int _useFlag; // Which flag USE will set (if any)
+
+ ActionType();
+
+ /**
+ * Load the data for the action
+ */
+ void load(Common::SeekableReadStream &s);
+};
+
+struct UseType: public ActionType {
+ Common::String _target;
+ Common::String _verb;
+
+ UseType();
+
+ /**
+ * Load the data for the UseType
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+ void load3DO(Common::SeekableReadStream &s);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+};
+
+class BaseObject {
+protected:
+ static SherlockEngine *_vm;
+protected:
+ /**
+ * This will check to see if the object has reached the end of a sequence.
+ * If it has, it switch to whichever next sequence should be started.
+ * @returns true if the end of a sequence was reached
+ */
+ bool checkEndOfSequence();
+
+ /**
+ * Scans through the sequences array and finds the designated sequence.
+ * It then sets the frame number of the start of that sequence
+ */
+ void setObjSequence(int seq, bool wait);
+public:
+ static bool _countCAnimFrames;
+public:
+ SpriteType _type; // Type of object/sprite
+ Common::String _description; // Description lines
+ byte *_sequences; // Holds animation sequences
+ ImageFile *_images; // Sprite images
+ ImageFrame *_imageFrame; // Pointer to shape in the images
+ int _walkCount; // Walk counter
+ int _allow; // Allowed UI commands
+ int _frameNumber; // Frame number in rame sequence to draw
+ Point32 _position; // Current position
+ Point32 _delta; // Momvement amount
+ Common::Point _oldPosition; // Old position
+ Common::Point _oldSize; // Image's old size
+ Point32 _goto; // Walk destination
+
+ int _lookFlag; // Which flag LOOK will set (if any)
+ int _requiredFlag[2]; // Object will be hidden if not set
+ Common::Point _noShapeSize; // Size of a NO_SHAPE
+ int _status; // Status (open/closed, moved/not)
+ int8 _misc; // Misc field -- use varies with type
+ int _maxFrames; // Number of frames
+ int _flags; // Tells if object can be walked behind
+ AType _aType; // Tells if this is an object, person, talk, etc.
+ int _lookFrames; // How many frames to play of the look anim before pausing
+ int _seqCounter; // How many times this sequence has been executed
+ PositionFacing _lookPosition; // Where to walk when examining object
+ int _lookcAnim;
+ int _seqStack; // Allows gosubs to return to calling frame
+ int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes
+ uint _descOffset; // Tells where description starts in DescText
+ int _seqCounter2; // Counter of calling frame sequence
+ uint _seqSize; // Tells where description starts
+ UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6
+ int _quickDraw; // Flag telling whether to use quick draw routine or not
+ int _scaleVal; // Tells how to scale the sprite
+ int _gotoSeq; // Used by Talk to tell which sequence to goto when able
+ int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen)
+ int _restoreSlot; // Used when talk returns to the previous sequence
+public:
+ BaseObject();
+ virtual ~BaseObject() {}
+ static void setVm(SherlockEngine *vm);
+
+ /**
+ * Returns true if the the object has an Allow Talk Code in the sequence that it's
+ * currently running, specified by the _talkSeq field of the object. If it's 0,
+ * then it's a regular sequence. If it's not 0 but below 128, then it's a Talk Sequence.
+ * If it's above 128, then it's one of the Listen sequences.
+ */
+ bool hasAborts() const;
+
+ /**
+ * Check the state of the object
+ */
+ void checkObject();
+
+ /**
+ * Checks for codes
+ * @param name The name to check for codes
+ * @param messages Provides a lookup list of messages that can be printed
+ * @returns 0 if no codes are found, 1 if codes were found
+ */
+ int checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
+
+ /**
+ * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker
+ * so that it points to the beginning of the sequence number's talk sequence in the object's
+ * sequence buffer
+ * @param seq Which sequence to use (if there's more than 1)
+ * @remarks 1: First talk seq, 2: second talk seq, etc.
+ */
+ virtual void setObjTalkSequence(int seq) {}
+};
+
+class Sprite: public BaseObject {
+public:
+ Common::String _name;
+ Common::String _examine; // Examine in-depth description
+ Common::String _pickUp; // Message for if you can't pick up object
+
+ WalkSequences _walkSequences; // Holds animation sequences
+ int _sequenceNumber; // Sequence being used
+ Common::Point _noShapeSize; // Size of a NO_SHAPE
+ int _status; // Status: open/closed, moved/not moved
+ int8 _misc; // Miscellaneous use
+
+ // Rose Tattoo fields
+ int _startSeq; // Frame sequence starts at
+ ImageFrame *_stopFrames[8]; // Stop/rest frame for each direction
+ ImageFile *_altImages; // Images used for alternate NPC sequences
+ int _altSeq; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq)
+ int _centerWalk; // Flag telling the walk code to offset the walk destination
+ Common::Point _adjust; // Fine tuning adjustment to position when drawn
+ int _oldWalkSequence;
+public:
+ Sprite(): BaseObject() { clear(); }
+ virtual ~Sprite() {}
+
+ static void setVm(SherlockEngine *vm) { _vm = vm; }
+
+ /**
+ * Reset the data for the sprite
+ */
+ void clear();
+
+ /**
+ * Updates the image frame poiner for the sprite
+ */
+ void setImageFrame();
+
+ /**
+ * Checks the sprite's position to see if it's collided with any special objects
+ */
+ void checkSprite();
+
+ /**
+ * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker
+ * so that it points to the beginning of the sequence number's talk sequence in the object's
+ * sequence buffer
+ * @param seq Which sequence to use (if there's more than 1)
+ * @remarks 1: First talk seq, 2: second talk seq, etc.
+ */
+ virtual void setObjTalkSequence(int seq) {}
+
+ /**
+ * Return frame width
+ */
+ int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; }
+
+ /**
+ * Return frame height
+ */
+ int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; }
+
+ /**
+ * Returns the old bounsd for the sprite from the previous frame
+ */
+ const Common::Rect getOldBounds() const;
+
+ /**
+ * This adjusts the sprites position, as well as it's animation sequence:
+ */
+ virtual void adjustSprite() = 0;
+
+ /**
+ * Bring a moving character using the sprite to a standing position
+ */
+ virtual void gotoStand() = 0;
+
+ /**
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
+ */
+ virtual void setWalking() = 0;
+};
+
+enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 };
+#define USE_COUNT 4
+
+class Object: public BaseObject {
+public:
+ Common::String _name; // Name
+ Common::String _examine; // Examine in-depth description
+ int _sequenceNumber;
+ int _sequenceOffset;
+ int _pickup;
+ int _defaultCommand; // Default right-click command
+
+ // Serrated Scalpel fields
+ int _pickupFlag; // Which flag PICKUP will set (if any)
+ ActionType _aOpen; // Holds data for moving object
+ ActionType _aClose;
+ ActionType _aMove;
+
+ Object();
+ virtual ~Object() {}
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+ void load3DO(Common::SeekableReadStream &s);
+
+ /**
+ * Toggle the type of an object between hidden and active
+ */
+ void toggleHidden();
+
+ /**
+ * Handle setting any flags associated with the object
+ */
+ void setFlagsAndToggles();
+
+ /**
+ * Adjusts the sprite's position and animation sequence, advancing by 1 frame.
+ * If the end of the sequence is reached, the appropriate action is taken.
+ */
+ void adjustObject();
+
+ /**
+ * Handles trying to pick up an object. If allowed, plays an y necessary animation for picking
+ * up the item, and then adds it to the player's inventory
+ */
+ int pickUpObject(FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
+
+ /**
+ * Return the frame width
+ */
+ int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; }
+
+ /**
+ * Return the frame height
+ */
+ int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; }
+
+ /**
+ * Returns the current bounds for the sprite
+ */
+ const Common::Rect getNewBounds() const;
+
+ /**
+ * Returns the bounds for a sprite without a shape
+ */
+ const Common::Rect getNoShapeBounds() const;
+
+ /**
+ * Returns the old bounsd for the sprite from the previous frame
+ */
+ const Common::Rect getOldBounds() const;
+
+ /**
+ * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker
+ * so that it points to the beginning of the sequence number's talk sequence in the object's
+ * sequence buffer
+ * @param seq Which sequence to use (if there's more than 1)
+ * @remarks 1: First talk seq, 2: second talk seq, etc.
+ */
+ virtual void setObjTalkSequence(int seq);
+};
+
+struct CAnim {
+ Common::String _name; // Name
+ Common::Point _position; // Position
+ int _dataSize; // Size of uncompressed animation data
+ uint32 _dataOffset; // offset within room file of animation data
+ int _flags; // Tells if can be walked behind
+ PositionFacing _goto[2]; // Position Holmes (and NPC in Rose Tattoo) should walk to before anim starts
+ PositionFacing _teleport[2]; // Location Holmes (and NPC) shoul teleport to after playing canim
+
+ // Scalpel specific
+ byte _sequences[MAX_FRAME]; // Animation sequences
+ SpriteType _type;
+
+ // Rose Tattoo specific
+ int _scaleVal; // How much the canim is scaled
+
+ /**
+ * Load the data for the animation
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset);
+ void load3DO(Common::SeekableReadStream &s, uint32 dataOffset);
+};
+
+struct SceneImage {
+ ImageFile *_images; // Object images
+ int _maxFrames; // How many frames in object
+ int _filesize; // File size
+
+ SceneImage();
+} ;
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp
new file mode 100644
index 0000000000..1e19dccb32
--- /dev/null
+++ b/engines/sherlock/people.cpp
@@ -0,0 +1,332 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/people.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/tattoo/tattoo_people.h"
+
+namespace Sherlock {
+
+// Characer animation sequences
+static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = {
+ { 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Right
+ { 22, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down
+ { 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Left
+ { 15, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up
+ { 42, 1, 2, 3, 4, 5, 0 }, // Goto Stand Right
+ { 47, 1, 2, 3, 4, 5, 0 }, // Goto Stand Down
+ { 42, 1, 2, 3, 4, 5, 0 }, // Goto Stand Left
+ { 36, 1, 0 }, // Goto Stand Up
+ { 8, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up Right
+ { 1, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down Right
+ { 8, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up Left
+ { 1, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down Left
+ { 37, 1, 2, 3, 4, 5, 0 }, // Goto Stand Up Right
+ { 37, 1, 2, 3, 4, 5, 0 }, // Goto Stand Up Left
+ { 52, 1, 2, 3, 4, 0 }, // Goto Stand Down Right
+ { 52, 1, 2, 3, 4, 0 } // Goto Stand Down Left
+};
+
+// Rose Tattoo walk image libraries
+// Walk resources within WALK.LIB
+const char *const WALK_LIB_NAMES[NUM_IN_WALK_LIB] = {
+ "SVGAWALK.VGS",
+ "COATWALK.VGS",
+ "WATSON.VGS",
+ "NOHAT.VGS",
+ "TUPRIGHT.VGS",
+ "TRIGHT.VGS",
+ "TDOWNRG.VGS",
+ "TWUPRIGH.VGS",
+ "TWRIGHT.VGS",
+ "TWDOWNRG.VGS"
+};
+
+/*----------------------------------------------------------------*/
+
+Person::Person() : Sprite() {
+ _walkLoaded = false;
+ _oldWalkSequence = -1;
+ _srcZone = _destZone = 0;
+}
+
+void Person::goAllTheWay() {
+ Scene &scene = *_vm->_scene;
+ Common::Point srcPt = getSourcePoint();
+
+ // Get the zone the player is currently in
+ _srcZone = scene.whichZone(srcPt);
+ if (_srcZone == -1)
+ _srcZone = scene.closestZone(srcPt);
+
+ // Get the zone of the destination
+ _destZone = scene.whichZone(_walkDest);
+ if (_destZone == -1) {
+ _destZone = scene.closestZone(_walkDest);
+
+ // Check for any restriction of final destination position
+ _walkDest = _vm->_people->restrictToZone(_destZone, _walkDest);
+ }
+
+ // Only do a walk if both zones are acceptable
+ if (_srcZone == -2 || _destZone == -2)
+ return;
+
+ // If the start and dest zones are the same, walk directly to the dest point
+ if (_srcZone == _destZone) {
+ setWalking();
+ } else {
+ // Otherwise a path needs to be formed from the path information
+ int i = scene._walkDirectory[_srcZone][_destZone];
+
+ // See if we need to use a reverse path
+ if (i == -1)
+ i = scene._walkDirectory[_destZone][_srcZone];
+
+ const WalkArray &points = scene._walkPoints[i];
+
+ // See how many points there are between the src and dest zones
+ if (!points._pointsCount || points._pointsCount == -1) {
+ // There are none, so just walk to the new zone
+ setWalking();
+ } else {
+ // There are points, so set up a multi-step path between points
+ // to reach the given destination
+ _walkTo.clear();
+
+ if (scene._walkDirectory[_srcZone][_destZone] != -1) {
+ for (int idx = (int)points.size() - 1; idx >= 0; --idx)
+ _walkTo.push(points[idx]);
+ } else {
+ for (int idx = 0; idx < (int)points.size(); ++idx) {
+ _walkTo.push(points[idx]);
+ }
+ }
+
+ // Final position
+ _walkTo.push(_walkDest);
+
+ // Start walking
+ _walkDest = _walkTo.pop();
+ setWalking();
+ }
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+People *People::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelPeople(vm);
+ else
+ return new Tattoo::TattooPeople(vm);
+}
+
+People::People(SherlockEngine *vm) : _vm(vm) {
+ _holmesOn = true;
+ _allowWalkAbort = true;
+ _portraitLoaded = false;
+ _portraitsOn = true;
+ _clearingThePortrait = false;
+ _talkPics = nullptr;
+ _portraitSide = 0;
+ _speakerFlip = false;
+ _holmesFlip = false;
+ _holmesQuotient = 0;
+ _savedPos = Point32(-1, -1);
+ _savedPos._facing = -1;
+ _forceWalkReload = false;
+ _useWalkLib = false;
+ _walkControl = 0;
+
+ _portrait._sequences = new byte[32];
+}
+
+People::~People() {
+ for (uint idx = 0; idx < _data.size(); ++idx) {
+ if (_data[idx]->_walkLoaded)
+ delete _data[idx]->_images;
+ delete _data[idx];
+ }
+
+ delete _talkPics;
+ delete[] _portrait._sequences;
+}
+
+void People::reset() {
+ SaveManager &saves = *_vm->_saves;
+ Talk &talk = *_vm->_talk;
+ _data[HOLMES]->_description = "Sherlock Holmes!";
+
+ // Note: Serrated Scalpel only uses a single Person slot for Sherlock.. Watson is handled by scene sprites
+ int count = IS_SERRATED_SCALPEL ? 1 : MAX_CHARACTERS;
+ for (int idx = 0; idx < count; ++idx) {
+ Person &p = *_data[idx];
+
+ if (IS_SERRATED_SCALPEL) {
+ p._type = CHARACTER;
+ p._sequenceNumber = (int)Tattoo::STOP_DOWNRIGHT;
+ p._position = Point32(100 * FIXED_INT_MULTIPLIER, 110 * FIXED_INT_MULTIPLIER);
+ } else if (!talk._scriptMoreFlag && !saves._justLoaded) {
+ p._type = (idx == 0) ? CHARACTER : INVALID;
+ p._sequenceNumber = (int)Scalpel::STOP_DOWNRIGHT;
+ p._position = Point32(36 * FIXED_INT_MULTIPLIER, 29 * FIXED_INT_MULTIPLIER);
+ p._use[0]._verb = "";
+ p._use[1]._verb = "";
+ }
+
+ p._imageFrame = nullptr;
+ p._frameNumber = 1;
+ p._delta = Point32(0, 0);
+ p._oldPosition = Common::Point(0, 0);
+ p._oldSize = Common::Point(0, 0);
+ p._misc = 0;
+ p._walkCount = 0;
+ p._pickUp = "";
+ p._allow = 0;
+ p._noShapeSize = Common::Point(0, 0);
+ p._goto = Common::Point(0, 0);
+ p._status = 0;
+ p._seqTo = 0;
+ p._seqCounter = p._seqCounter2 = 0;
+ p._seqStack = 0;
+ p._gotoSeq = p._talkSeq = 0;
+ p._restoreSlot = 0;
+ p._startSeq = 0;
+ p._altImages = nullptr;
+ p._altSeq = 0;
+ p._centerWalk = true;
+ p._adjust = Common::Point(0, 0);
+
+ // Load the default walk sequences
+ p._walkTo.clear();
+ p._oldWalkSequence = -1;
+ p._walkSequences.clear();
+ if (IS_SERRATED_SCALPEL) {
+ p._walkSequences.resize(MAX_HOLMES_SEQUENCE);
+ for (int seqIdx = 0; seqIdx < MAX_HOLMES_SEQUENCE; ++seqIdx) {
+ p._walkSequences[seqIdx]._sequences.clear();
+
+ const byte *pSrc = &CHARACTER_SEQUENCES[seqIdx][0];
+ do {
+ p._walkSequences[seqIdx]._sequences.push_back(*pSrc);
+ } while (*pSrc++);
+ }
+ }
+ }
+}
+
+bool People::freeWalk() {
+ bool result = false;
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (_data[idx]->_walkLoaded) {
+ delete _data[idx]->_images;
+ _data[idx]->_images = nullptr;
+
+ _data[idx]->_walkLoaded = false;
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+int People::findSpeaker(int speaker) {
+ Scene &scene = *_vm->_scene;
+ const char *portrait = _characters[speaker]._portrait;
+
+ for (int idx = 0; idx < (int)scene._bgShapes.size(); ++idx) {
+ Object &obj = scene._bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE) {
+ Common::String name(obj._name.c_str(), obj._name.c_str() + 4);
+
+ if (name.equalsIgnoreCase(portrait)
+ && obj._name[4] >= '0' && obj._name[4] <= '9')
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+void People::clearTalking() {
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+
+ if (_portraitsOn) {
+ Common::Point pt = _portrait._position;
+ int width, height;
+ _portrait._imageFrame = _talkPics ? &(*_talkPics)[0] : (ImageFrame *)nullptr;
+
+ // Flag portrait for removal, and save the size of the frame to use erasing it
+ _portrait._type = REMOVE;
+ _portrait._delta.x = width = _portrait.frameWidth();
+ _portrait._delta.y = height = _portrait.frameHeight();
+
+ delete _talkPics;
+ _talkPics = nullptr;
+
+ // Flag to let the talk code know not to interrupt on the next doBgAnim
+ _clearingThePortrait = true;
+ scene.doBgAnim();
+ _clearingThePortrait = false;
+
+ screen.slamArea(pt.x, pt.y, width, height);
+
+ if (!talk._talkToAbort)
+ _portraitLoaded = false;
+ }
+}
+
+void People::synchronize(Serializer &s) {
+ s.syncAsByte(_holmesOn);
+
+ if (IS_SERRATED_SCALPEL) {
+ s.syncAsSint16LE(_data[HOLMES]->_position.x);
+ s.syncAsSint16LE(_data[HOLMES]->_position.y);
+ s.syncAsSint16LE(_data[HOLMES]->_sequenceNumber);
+ } else {
+ for (uint idx = 0; idx < _data.size(); ++idx) {
+ Person &p = *_data[idx];
+ s.syncAsSint16LE(p._position.x);
+ s.syncAsSint16LE(p._position.y);
+ s.syncAsSint16LE(p._sequenceNumber);
+ s.syncAsSint16LE(p._type);
+ s.syncString(p._walkVGSName);
+ s.syncString(p._description);
+ s.syncString(p._examine);
+ }
+ }
+
+ s.syncAsSint16LE(_holmesQuotient);
+
+ if (s.isLoading()) {
+ _savedPos = _data[HOLMES]->_position;
+ _savedPos._facing = _data[HOLMES]->_sequenceNumber;
+ }
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h
new file mode 100644
index 0000000000..b59522e4b8
--- /dev/null
+++ b/engines/sherlock/people.h
@@ -0,0 +1,168 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_PEOPLE_H
+#define SHERLOCK_PEOPLE_H
+
+#include "common/scummsys.h"
+#include "common/queue.h"
+#include "sherlock/objects.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+enum PeopleId {
+ HOLMES = 0,
+ WATSON = 1,
+ MAX_NPC_PATH = 200
+};
+
+enum {
+ MAP_UP = 1, MAP_UPRIGHT = 2, MAP_RIGHT = 1, MAP_DOWNRIGHT = 4,
+ MAP_DOWN = 5, MAP_DOWNLEFT = 6, MAP_LEFT = 2, MAP_UPLEFT = 8
+};
+
+#define NUM_IN_WALK_LIB 10
+extern const char *const WALK_LIB_NAMES[10];
+
+#define MAX_CHARACTERS (IS_SERRATED_SCALPEL ? 1 : 6)
+
+struct PersonData {
+ const char *_name;
+ const char *_portrait;
+ const byte *_stillSequences;
+ const byte *_talkSequences;
+
+ PersonData(const char *name, const char *portrait, const byte *stillSequences, const byte *talkSequences) :
+ _name(name), _portrait(portrait), _stillSequences(stillSequences), _talkSequences(talkSequences) {}
+};
+
+class Person : public Sprite {
+protected:
+ /**
+ * Get the source position for a character potentially affected by scaling
+ */
+ virtual Common::Point getSourcePoint() const = 0;
+public:
+ Common::Queue<Common::Point> _walkTo;
+ int _srcZone, _destZone;
+ bool _walkLoaded;
+ Common::String _portrait;
+ Common::Point _walkDest;
+ Common::String _npcName;
+
+ // Rose Tattoo fields
+ Common::String _walkVGSName; // Name of walk library person is using
+public:
+ Person();
+ virtual ~Person() {}
+
+ /**
+ * Called to set the character walking to the current cursor location.
+ * It uses the zones and the inter-zone points to determine a series
+ * of steps to walk to get to that position.
+ */
+ void goAllTheWay();
+
+ /**
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir) = 0;
+};
+
+class SherlockEngine;
+
+class People {
+protected:
+ SherlockEngine *_vm;
+ Common::Array<Person *> _data;
+
+ People(SherlockEngine *vm);
+public:
+ Common::Array<PersonData> _characters;
+ ImageFile *_talkPics;
+ PositionFacing _savedPos;
+ bool _holmesOn;
+ bool _portraitLoaded;
+ bool _portraitsOn;
+ Object _portrait;
+ bool _clearingThePortrait;
+ bool _allowWalkAbort;
+ int _portraitSide;
+ bool _speakerFlip;
+ bool _holmesFlip;
+ int _holmesQuotient;
+ bool _forceWalkReload;
+ bool _useWalkLib;
+
+ int _walkControl;
+public:
+ static People *init(SherlockEngine *vm);
+ virtual ~People();
+
+ Person &operator[](PeopleId id) { return *_data[id]; }
+ Person &operator[](int idx) { return *_data[idx]; }
+
+ /**
+ * Reset the player data
+ */
+ void reset();
+
+ /**
+ * If the walk data has been loaded, then it will be freed
+ */
+ bool freeWalk();
+
+ /**
+ * Turn off any currently active portraits, and removes them from being drawn
+ */
+ void clearTalking();
+
+ /**
+ * Finds the scene background object corresponding to a specified speaker
+ */
+ virtual int findSpeaker(int speaker);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s) = 0;
+
+ /**
+ * Change the sequence of the scene background object associated with the current speaker.
+ */
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1) = 0;
+
+ /**
+ * Load the walking images for Sherlock
+ */
+ virtual bool loadWalk() = 0;
+
+ /**
+ * Restrict passed point to zone using Sherlock's positioning rules
+ */
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos) = 0;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp
new file mode 100644
index 0000000000..206d7173ac
--- /dev/null
+++ b/engines/sherlock/resources.cpp
@@ -0,0 +1,372 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/resources.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+
+namespace Sherlock {
+
+Cache::Cache(SherlockEngine *vm) : _vm(vm) {
+}
+
+bool Cache::isCached(const Common::String &filename) const {
+ return _resources.contains(filename);
+}
+
+void Cache::load(const Common::String &name) {
+ // First check if the entry already exists
+ if (_resources.contains(name))
+ return;
+
+ // Open the file for reading
+ Common::File f;
+ if (!f.open(name))
+ error("Could not read file - %s", name.c_str());
+
+ load(name, f);
+
+ f.close();
+}
+
+void Cache::load(const Common::String &name, Common::SeekableReadStream &stream) {
+ // First check if the entry already exists
+ if (_resources.contains(name))
+ return;
+
+ int32 signature = stream.readUint32BE();
+ stream.seek(0);
+
+ // Allocate a new cache entry
+ _resources[name] = CacheEntry();
+ CacheEntry &cacheEntry = _resources[name];
+
+ // Check whether the file is compressed
+ if (signature == MKTAG('L', 'Z', 'V', 26)) {
+ // It's compressed, so decompress the file and store it's data in the cache entry
+ Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream);
+ cacheEntry.resize(decompressed->size());
+ decompressed->read(&cacheEntry[0], decompressed->size());
+
+ delete decompressed;
+ } else {
+ // It's not, so read the raw data of the file into the cache entry
+ cacheEntry.resize(stream.size());
+ stream.read(&cacheEntry[0], stream.size());
+ }
+}
+
+Common::SeekableReadStream *Cache::get(const Common::String &filename) const {
+ // Return a memory stream that encapsulates the data
+ const CacheEntry &cacheEntry = _resources[filename];
+ return new Common::MemoryReadStream(&cacheEntry[0], cacheEntry.size());
+}
+
+/*----------------------------------------------------------------*/
+
+Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) {
+ _resourceIndex = -1;
+
+ if (_vm->_interactiveFl) {
+ if (!IS_3DO) {
+ addToCache("vgs.lib");
+ addToCache("talk.lib");
+ addToCache("journal.txt");
+
+ if (IS_SERRATED_SCALPEL) {
+ addToCache("sequence.txt");
+ addToCache("portrait.lib");
+ } else {
+ addToCache("walk.lib");
+ }
+ } else {
+ // 3DO
+
+ // ITEM data from VGS.LIB is in ITEM.LIB
+ addToCache("item.lib");
+
+ // talk.lib - resources themselves seem to be the same, although a few texts were slightly changed
+ addToCache("talk.lib");
+
+ // remaining files are missing
+ // portraits were replaced with FMV
+ }
+ }
+}
+
+void Resources::addToCache(const Common::String &filename) {
+ _cache.load(filename);
+
+ // Check to see if the file is a library
+ Common::SeekableReadStream *stream = load(filename);
+ uint32 header = stream->readUint32BE();
+
+ if (header == MKTAG('L', 'I', 'B', 26))
+ loadLibraryIndex(filename, stream, false);
+ else if (header == MKTAG('L', 'I', 'C', 26))
+ loadLibraryIndex(filename, stream, true);
+
+ delete stream;
+}
+
+void Resources::addToCache(const Common::String &filename, const Common::String &libFilename) {
+ // Get the resource
+ Common::SeekableReadStream *stream = load(filename, libFilename);
+
+ _cache.load(filename, *stream);
+
+ delete stream;
+}
+
+void Resources::addToCache(const Common::String &filename, Common::SeekableReadStream &stream) {
+ _cache.load(filename, stream);
+}
+
+Common::SeekableReadStream *Resources::load(const Common::String &filename) {
+ // First check if the file is directly in the cache
+ if (_cache.isCached(filename))
+ return _cache.get(filename);
+
+ // Secondly, iterate through any loaded library file looking for a resource
+ // that has the same name
+ for (LibraryIndexes::iterator i = _indexes.begin(); i != _indexes.end(); ++i) {
+ if (i->_value.contains(filename)) {
+ // Get a stream reference to the given library file
+ Common::SeekableReadStream *stream = load(i->_key);
+ LibraryEntry &entry = i->_value[filename];
+ _resourceIndex = entry._index;
+
+ stream->seek(entry._offset);
+ Common::SeekableReadStream *resStream = stream->readStream(entry._size);
+ decompressIfNecessary(resStream);
+
+ delete stream;
+ return resStream;
+ }
+ }
+
+ // At this point, fall back on a physical file with the given name
+ Common::File f;
+ if (!f.open(filename))
+ error("Could not load file - %s", filename.c_str());
+
+ Common::SeekableReadStream *stream = f.readStream(f.size());
+ f.close();
+ decompressIfNecessary(stream);
+
+ return stream;
+}
+
+void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) {
+ bool isCompressed = stream->readUint32BE() == MKTAG('L', 'Z', 'V', 26);
+
+ if (isCompressed) {
+ int outSize = stream->readUint32LE();
+ Common::SeekableReadStream *newStream = decompressLZ(*stream, outSize);
+ delete stream;
+ stream = newStream;
+ } else {
+ stream->seek(-4, SEEK_CUR);
+ }
+}
+
+Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile) {
+ // Open up the library for access
+ Common::SeekableReadStream *libStream = load(libraryFile);
+
+ // Check if the library has already had it's index read, and if not, load it
+ if (!_indexes.contains(libraryFile))
+ loadLibraryIndex(libraryFile, libStream, false);
+
+ // Extract the data for the specified resource and return it
+ LibraryEntry &entry = _indexes[libraryFile][filename];
+ libStream->seek(entry._offset);
+ Common::SeekableReadStream *stream = libStream->readStream(entry._size);
+ decompressIfNecessary(stream);
+
+ delete libStream;
+ return stream;
+}
+
+bool Resources::exists(const Common::String &filename) const {
+ Common::File f;
+ return f.exists(filename) || _cache.isCached(filename);
+}
+
+void Resources::loadLibraryIndex(const Common::String &libFilename,
+ Common::SeekableReadStream *stream, bool isNewStyle) {
+ uint32 offset, nextOffset;
+
+ // Create an index entry
+ _indexes[libFilename] = LibraryIndex();
+ LibraryIndex &index = _indexes[libFilename];
+
+ // Read in the number of resources
+ stream->seek(4);
+ int count = 0;
+
+ if (!IS_3DO) {
+ // PC
+ count = stream->readUint16LE();
+
+ if (isNewStyle)
+ stream->seek((count + 1) * 8, SEEK_CUR);
+
+ // Loop through reading in the entries
+ for (int idx = 0; idx < count; ++idx) {
+ // Read the name of the resource
+ char resName[13];
+ stream->read(resName, 13);
+ resName[12] = '\0';
+
+ // Read the offset
+ offset = stream->readUint32LE();
+
+ if (idx == (count - 1)) {
+ nextOffset = stream->size();
+ } else {
+ // Read the size by jumping forward to read the next entry's offset
+ stream->seek(13, SEEK_CUR);
+ nextOffset = stream->readUint32LE();
+ stream->seek(-17, SEEK_CUR);
+ }
+
+ // Add the entry to the index
+ index[resName] = LibraryEntry(idx, offset, nextOffset - offset);
+ }
+
+ } else {
+ // 3DO
+ count = stream->readUint16BE();
+
+ // 3DO header
+ // Loop through reading in the entries
+
+ // Read offset of first entry
+ offset = stream->readUint32BE();
+
+ for (int idx = 0; idx < count; ++idx) {
+
+ // Read the name of the resource
+ char resName[13];
+ stream->read(resName, 13);
+ resName[12] = '\0';
+
+ stream->skip(3); // filler
+
+ if (idx == (count - 1)) {
+ nextOffset = stream->size();
+ } else {
+ // Read the offset of the next entry
+ nextOffset = stream->readUint32BE();
+ }
+
+ // Add the entry to the index
+ index[resName] = LibraryEntry(idx, offset, nextOffset - offset);
+
+ // use next offset as current offset
+ offset = nextOffset;
+ }
+ }
+}
+
+int Resources::resourceIndex() const {
+ return _resourceIndex;
+}
+
+Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source) {
+ // This variation can't be used by Rose Tattoo, since compressed resources include the input size,
+ // not the output size. Which means their decompression has to be done via passed buffers
+ assert(IS_SERRATED_SCALPEL);
+
+ uint32 id = source.readUint32BE();
+ assert(id == MKTAG('L', 'Z', 'V', 0x1A));
+
+ uint32 outputSize = source.readUint32LE();
+ return decompressLZ(source, outputSize);
+}
+
+Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) {
+ int inSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1;
+ byte *outBuffer = (byte *)malloc(outSize);
+ Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES);
+
+ decompressLZ(source, outBuffer, outSize, inSize);
+
+ return outStream;
+}
+
+void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) {
+ int inputSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1;
+
+ decompressLZ(source, buffer, outSize, inputSize);
+}
+
+Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream &source, uint32 outSize) {
+ byte *dataOut = (byte *)malloc(outSize);
+ decompressLZ(source, dataOut, outSize, -1);
+
+ return new Common::MemoryReadStream(dataOut, outSize, DisposeAfterUse::YES);
+}
+
+void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize) {
+ byte lzWindow[4096];
+ uint16 lzWindowPos;
+ uint16 cmd;
+
+ byte *outBufferEnd = outBuffer + outSize;
+ int endPos = source.pos() + inSize;
+
+ memset(lzWindow, 0xFF, 0xFEE);
+ lzWindowPos = 0xFEE;
+ cmd = 0;
+
+ do {
+ cmd >>= 1;
+ if (!(cmd & 0x100))
+ cmd = source.readByte() | 0xFF00;
+
+ if (cmd & 1) {
+ byte literal = source.readByte();
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ } else {
+ int copyPos, copyLen;
+ copyPos = source.readByte();
+ copyLen = source.readByte();
+ copyPos = copyPos | ((copyLen & 0xF0) << 4);
+ copyLen = (copyLen & 0x0F) + 3;
+ while (copyLen--) {
+ byte literal = lzWindow[copyPos];
+ copyPos = (copyPos + 1) & 0x0FFF;
+ *outBuffer++ = literal;
+ lzWindow[lzWindowPos] = literal;
+ lzWindowPos = (lzWindowPos + 1) & 0x0FFF;
+ }
+ }
+ } while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos));
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/resources.h b/engines/sherlock/resources.h
new file mode 100644
index 0000000000..8e0216d69d
--- /dev/null
+++ b/engines/sherlock/resources.h
@@ -0,0 +1,170 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_RESOURCES_H
+#define SHERLOCK_RESOURCES_H
+
+#include "common/array.h"
+#include "common/file.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "common/stream.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+typedef Common::Array<byte> CacheEntry;
+typedef Common::HashMap<Common::String, CacheEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> CacheHash;
+
+struct LibraryEntry {
+ uint32 _offset, _size;
+ int _index;
+
+ LibraryEntry() : _index(0), _offset(0), _size(0) {}
+ LibraryEntry(int index, uint32 offset, uint32 size) :
+ _index(index), _offset(offset), _size(size) {}
+};
+typedef Common::HashMap<Common::String, LibraryEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> LibraryIndex;
+typedef Common::HashMap<Common::String, LibraryIndex, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> LibraryIndexes;
+
+class SherlockEngine;
+
+class Cache {
+private:
+ SherlockEngine *_vm;
+ CacheHash _resources;
+public:
+ Cache(SherlockEngine *_vm);
+
+ /**
+ * Returns true if a given file is currently being cached
+ */
+ bool isCached(const Common::String &filename) const;
+
+ /**
+ * Loads a file into the cache if it's not already present, and returns it.
+ * If the file is LZW compressed, automatically decompresses it and loads
+ * the uncompressed version into memory
+ */
+ void load(const Common::String &name);
+
+ /**
+ * Load a cache entry based on a passed stream
+ */
+ void load(const Common::String &name, Common::SeekableReadStream &stream);
+
+ /**
+ * Get a file from the cache
+ */
+ Common::SeekableReadStream *get(const Common::String &filename) const;
+};
+
+class Resources {
+private:
+ SherlockEngine *_vm;
+ Cache _cache;
+ LibraryIndexes _indexes;
+ int _resourceIndex;
+
+ /**
+ * Reads in the index from a library file, and caches it's index for later use
+ */
+ void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle);
+public:
+ Resources(SherlockEngine *vm);
+
+ /**
+ * Adds the specified file to the cache. If it's a library file, takes care of
+ * loading it's index for future use
+ */
+ void addToCache(const Common::String &filename);
+
+ /**
+ * Adds a resource from a library file to the cache
+ */
+ void addToCache(const Common::String &filename, const Common::String &libFilename);
+
+ /**
+ * Adds a given stream to the cache under the given name
+ */
+ void addToCache(const Common::String &filename, Common::SeekableReadStream &stream);
+
+ bool isInCache(const Common::String &filename) const { return _cache.isCached(filename); }
+
+ /**
+ * Checks the passed stream, and if is compressed, deletes it and replaces it with it's uncompressed data
+ */
+ void decompressIfNecessary(Common::SeekableReadStream *&stream);
+
+ /**
+ * Returns a stream for a given file
+ */
+ Common::SeekableReadStream *load(const Common::String &filename);
+
+ /**
+ * Loads a specific resource from a given library file
+ */
+ Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile);
+
+ /**
+ * Returns true if the given file exists on disk or in the cache
+ */
+ bool exists(const Common::String &filename) const;
+
+ /**
+ * Returns the index of the last loaded resource in it's given library file.
+ * This will be used primarily when loading talk files, so the engine can
+ * update the given conversation number in the journal
+ */
+ int resourceIndex() const;
+
+ /**
+ * Decompresses LZW compressed data
+ */
+ Common::SeekableReadStream *decompress(Common::SeekableReadStream &source);
+
+ /**
+ * Decompresses LZW compressed data
+ */
+ Common::SeekableReadStream *decompress(Common::SeekableReadStream &source, uint32 outSize);
+
+ /**
+ * Decompresses LZW compressed data
+ */
+ void decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize);
+
+ /**
+ * Decompresses LZW compressed data
+ */
+ static Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, uint32 outSize);
+
+ /**
+ * Decompresses LZW compressed data
+ */
+ static void decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize);
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/saveload.cpp b/engines/sherlock/saveload.cpp
new file mode 100644
index 0000000000..fae8196dc1
--- /dev/null
+++ b/engines/sherlock/saveload.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/saveload.h"
+#include "sherlock/surface.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "common/system.h"
+#include "graphics/scaler.h"
+#include "graphics/thumbnail.h"
+
+namespace Sherlock {
+
+const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-";
+static const char *const SAVEGAME_STR = "SHLK";
+#define SAVEGAME_STR_SIZE 4
+
+/*----------------------------------------------------------------*/
+
+SaveManager *SaveManager::init(SherlockEngine *vm, const Common::String &target) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelSaveManager(vm, target);
+ else
+ return new SaveManager(vm, target);
+}
+
+SaveManager::SaveManager(SherlockEngine *vm, const Common::String &target) :
+ _vm(vm), _target(target) {
+ _saveThumb = nullptr;
+ _envMode = SAVEMODE_NONE;
+ _justLoaded = false;
+ _savegameIndex = 0;
+}
+
+SaveManager::~SaveManager() {
+ if (_saveThumb) {
+ _saveThumb->free();
+ delete _saveThumb;
+ }
+}
+
+void SaveManager::createSavegameList() {
+ Screen &screen = *_vm->_screen;
+
+ _savegames.clear();
+ for (int idx = 0; idx < MAX_SAVEGAME_SLOTS; ++idx)
+ _savegames.push_back(EMPTY_SAVEGAME_SLOT);
+
+ SaveStateList saveList = getSavegameList(_target);
+ for (uint idx = 0; idx < saveList.size(); ++idx) {
+ int slot = saveList[idx].getSaveSlot() - 1;
+ if (slot >= 0 && slot < MAX_SAVEGAME_SLOTS)
+ _savegames[slot] = saveList[idx].getDescription();
+ }
+
+ // Ensure the names will fit on the screen
+ for (uint idx = 0; idx < _savegames.size(); ++idx) {
+ int width = screen.stringWidth(_savegames[idx]) + 24;
+ if (width > 308) {
+ // It won't fit in, so remove characters until it does
+ do {
+ width -= screen.charWidth(_savegames[idx].lastChar());
+ _savegames[idx].deleteLastChar();
+ } while (width > 300);
+ }
+ }
+}
+
+SaveStateList SaveManager::getSavegameList(const Common::String &target) {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String saveDesc;
+ Common::String pattern = Common::String::format("%s.0??", target.c_str());
+ SherlockSavegameHeader header;
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ const char *ext = strrchr(file->c_str(), '.');
+ int slot = ext ? atoi(ext + 1) : -1;
+
+ if (slot >= 0 && slot < MAX_SAVEGAME_SLOTS) {
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
+
+ if (in) {
+ if (!readSavegameHeader(in, header))
+ continue;
+
+ saveList.push_back(SaveStateDescriptor(slot, header._saveName));
+
+ header._thumbnail->free();
+ delete header._thumbnail;
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+bool SaveManager::readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHeader &header) {
+ char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
+ header._thumbnail = nullptr;
+
+ // Validate the header Id
+ in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
+ if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
+ return false;
+
+ header._version = in->readByte();
+ if (header._version < MINIMUM_SAVEGAME_VERSION || header._version > CURRENT_SAVEGAME_VERSION)
+ return false;
+
+ // Read in the string
+ header._saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0') header._saveName += ch;
+
+ // Get the thumbnail
+ header._thumbnail = Graphics::loadThumbnail(*in);
+ if (!header._thumbnail)
+ return false;
+
+ // Read in save date/time
+ header._year = in->readSint16LE();
+ header._month = in->readSint16LE();
+ header._day = in->readSint16LE();
+ header._hour = in->readSint16LE();
+ header._minute = in->readSint16LE();
+ header._totalFrames = in->readUint32LE();
+
+ return true;
+}
+
+void SaveManager::writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegameHeader &header) {
+ // Write out a savegame header
+ out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
+
+ out->writeByte(CURRENT_SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->write(header._saveName.c_str(), header._saveName.size());
+ out->writeByte('\0');
+
+ // Handle the thumbnail. If there's already one set by the game, create one
+ if (!_saveThumb)
+ createThumbnail();
+ Graphics::saveThumbnail(*out, *_saveThumb);
+
+ _saveThumb->free();
+ delete _saveThumb;
+ _saveThumb = nullptr;
+
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ out->writeSint16LE(td.tm_year + 1900);
+ out->writeSint16LE(td.tm_mon + 1);
+ out->writeSint16LE(td.tm_mday);
+ out->writeSint16LE(td.tm_hour);
+ out->writeSint16LE(td.tm_min);
+ out->writeUint32LE(_vm->_events->getFrameCounter());
+}
+
+void SaveManager::createThumbnail() {
+ if (_saveThumb) {
+ _saveThumb->free();
+ delete _saveThumb;
+ }
+
+ _saveThumb = new Graphics::Surface();
+
+ if (!IS_3DO) {
+ uint8 thumbPalette[PALETTE_SIZE];
+ _vm->_screen->getPalette(thumbPalette);
+ ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette);
+ } else {
+ ::createThumbnailFromScreen(_saveThumb);
+ }
+}
+
+void SaveManager::loadGame(int slot) {
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
+ generateSaveName(slot));
+ if (!saveFile)
+ return;
+
+ // Load the savaegame header
+ SherlockSavegameHeader header;
+ if (!readSavegameHeader(saveFile, header))
+ error("Invalid savegame");
+
+ if (header._thumbnail) {
+ header._thumbnail->free();
+ delete header._thumbnail;
+ }
+
+ // Synchronize the savegame data
+ Serializer s(saveFile, nullptr);
+ s.setSaveVersion(header._version);
+ synchronize(s);
+
+ delete saveFile;
+}
+
+void SaveManager::saveGame(int slot, const Common::String &name) {
+ Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
+ generateSaveName(slot));
+
+ SherlockSavegameHeader header;
+ header._saveName = name;
+ writeSavegameHeader(out, header);
+
+ // Synchronize the savegame data
+ Serializer s(nullptr, out);
+ s.setSaveVersion(CURRENT_SAVEGAME_VERSION);
+ synchronize(s);
+
+ out->finalize();
+ delete out;
+}
+
+Common::String SaveManager::generateSaveName(int slot) {
+ return Common::String::format("%s.%03d", _target.c_str(), slot);
+}
+
+void SaveManager::synchronize(Serializer &s) {
+ Inventory &inv = *_vm->_inventory;
+ Journal &journal = *_vm->_journal;
+ Map &map = *_vm->_map;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+
+ int oldFont = screen.fontNumber();
+
+ inv.synchronize(s);
+ journal.synchronize(s);
+ people.synchronize(s);
+ map.synchronize(s);
+ scene.synchronize(s);
+ screen.synchronize(s);
+ talk.synchronize(s);
+ _vm->synchronize(s);
+
+ if (screen.fontNumber() != oldFont)
+ journal.resetPosition();
+
+ _justLoaded = true;
+}
+
+bool SaveManager::isSlotEmpty(int slot) const {
+ return _savegames[slot].equalsIgnoreCase(EMPTY_SAVEGAME_SLOT);
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/saveload.h b/engines/sherlock/saveload.h
new file mode 100644
index 0000000000..0aaa9cf801
--- /dev/null
+++ b/engines/sherlock/saveload.h
@@ -0,0 +1,151 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SAVELOAD_H
+#define SHERLOCK_SAVELOAD_H
+
+#include "common/scummsys.h"
+#include "common/savefile.h"
+#include "common/serializer.h"
+#include "common/str-array.h"
+#include "engines/savestate.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+#define MAX_SAVEGAME_SLOTS 99
+#define ONSCREEN_FILES_COUNT 5
+
+enum {
+ CURRENT_SAVEGAME_VERSION = 2,
+ MINIMUM_SAVEGAME_VERSION = 2
+};
+
+enum SaveMode { SAVEMODE_NONE = 0, SAVEMODE_LOAD = 1, SAVEMODE_SAVE = 2 };
+
+extern const char *const EMPTY_SAVEGAME_SLOT;
+
+struct SherlockSavegameHeader {
+ uint8 _version;
+ Common::String _saveName;
+ Graphics::Surface *_thumbnail;
+ int _year, _month, _day;
+ int _hour, _minute;
+ int _totalFrames;
+};
+
+class SherlockEngine;
+
+
+/**
+ * Derived serializer class with extra synchronization types
+ */
+class Serializer : public Common::Serializer {
+public:
+ Serializer(Common::SeekableReadStream *in, Common::WriteStream *out) : Common::Serializer(in, out) {}
+
+ /**
+ * New method to allow setting the version
+ */
+ void setSaveVersion(byte version) { _version = version; }
+};
+
+class SaveManager {
+protected:
+ SherlockEngine *_vm;
+ Common::String _target;
+ Graphics::Surface *_saveThumb;
+
+ /**
+ * Build up a savegame list, with empty slots given an explicit Empty message
+ */
+ void createSavegameList();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+public:
+ Common::StringArray _savegames;
+ int _savegameIndex;
+ SaveMode _envMode;
+ bool _justLoaded;
+public:
+ static SaveManager *init(SherlockEngine *vm, const Common::String &target);
+ SaveManager(SherlockEngine *vm, const Common::String &target);
+ virtual ~SaveManager();
+
+ /**
+ * Creates a thumbnail for the current on-screen contents
+ */
+ void createThumbnail();
+
+ /**
+ * Load a list of savegames
+ */
+ static SaveStateList getSavegameList(const Common::String &target);
+
+ /**
+ * Support method that generates a savegame name
+ * @param slot Slot number
+ */
+ Common::String generateSaveName(int slot);
+
+ /**
+ * Write out the header information for a savegame
+ */
+ void writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegameHeader &header);
+
+ /**
+ * Read in the header information for a savegame
+ */
+ static bool readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHeader &header);
+
+ /**
+ * Return the index of the button the mouse is over, if any
+ */
+ int getHighlightedButton() const;
+
+ /**
+ * Handle highlighting buttons
+ */
+ void highlightButtons(int btnIndex);
+
+ /**
+ * Load the game in the specified slot
+ */
+ void loadGame(int slot);
+
+ /**
+ * Save the game in the specified slot with the given name
+ */
+ void saveGame(int slot, const Common::String &name);
+
+ /**
+ * Returns true if the given save slot is empty
+ */
+ bool isSlotEmpty(int slot) const;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp
new file mode 100644
index 0000000000..8e8f99bc19
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp
@@ -0,0 +1,510 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/3do.h"
+
+#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "image/codecs/cinepak.h"
+
+// for Test-Code
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "engines/engine.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+namespace Sherlock {
+
+Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder()
+ : _stream(0), _videoTrack(0), _audioTrack(0) {
+ _streamVideoOffset = 0;
+ _streamAudioOffset = 0;
+}
+
+Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() {
+ close();
+}
+
+bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
+ uint32 videoSubType = 0;
+ uint32 videoCodecTag = 0;
+ uint32 videoHeight = 0;
+ uint32 videoWidth = 0;
+ uint32 videoFrameCount = 0;
+ uint32 audioSubType = 0;
+ uint32 audioCodecTag = 0;
+ uint32 audioChannels = 0;
+ uint32 audioSampleRate = 0;
+
+ close();
+
+ _stream = stream;
+ _streamVideoOffset = 0;
+ _streamAudioOffset = 0;
+
+ // Look for packets that we care about
+ static const int maxPacketCheckCount = 20;
+ for (int i = 0; i < maxPacketCheckCount; i++) {
+ uint32 chunkTag = _stream->readUint32BE();
+ uint32 chunkSize = _stream->readUint32BE() - 8;
+
+ // Bail out if done
+ if (_stream->eos())
+ break;
+
+ uint32 dataStartOffset = _stream->pos();
+
+ switch (chunkTag) {
+ case MKTAG('F','I','L','M'): {
+ // See if this is a FILM header
+ _stream->skip(4); // time stamp (based on 240 per second)
+ _stream->skip(4); // Unknown 0x00000000
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // FILM header found
+ if (_videoTrack) {
+ warning("Sherlock 3DO movie: Multiple FILM headers found");
+ close();
+ return false;
+ }
+ _stream->readUint32BE();
+ videoCodecTag = _stream->readUint32BE();
+ videoHeight = _stream->readUint32BE();
+ videoWidth = _stream->readUint32BE();
+ _stream->skip(4); // time scale
+ videoFrameCount = _stream->readUint32BE();
+
+ _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount);
+ addTrack(_videoTrack);
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ break;
+
+ default:
+ warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ close();
+ return false;
+ }
+ break;
+ }
+
+ case MKTAG('S','N','D','S'): {
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Audio header
+
+ // Bail if we already have a track
+ if (_audioTrack) {
+ warning("Sherlock 3DO movie: Multiple SNDS headers found");
+ close();
+ return false;
+ }
+
+ // OK, this is the start of a audio stream
+ _stream->readUint32BE(); // Version, always 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000008 ?!
+ _stream->readUint32BE(); // Unknown 0x00007500
+ _stream->readUint32BE(); // Unknown 0x00004000
+ _stream->readUint32BE(); // Unknown 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000010
+ audioSampleRate = _stream->readUint32BE();
+ audioChannels = _stream->readUint32BE();
+ audioCodecTag = _stream->readUint32BE();
+ _stream->readUint32BE(); // Unknown 0x00000004 compression ratio?
+ _stream->readUint32BE(); // Unknown 0x00000A2C
+
+ _audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels);
+ addTrack(_audioTrack);
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Audio data
+ break;
+ default:
+ warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ close();
+ return false;
+ }
+ break;
+ }
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
+ case MKTAG('D','A','C','Q'):
+ case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
+ // Ignore these chunks
+ break;
+
+ case MKTAG('S','H','D','R'):
+ // Happens for EA logo, seems to be garbage data right at the start of the file
+ break;
+
+ default:
+ warning("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+ close();
+ return false;
+ }
+
+ if ((_videoTrack) && (_audioTrack))
+ break;
+
+ // Seek to next chunk
+ _stream->seek(dataStartOffset + chunkSize);
+ }
+
+ // Bail if we didn't find video + audio
+ if ((!_videoTrack) || (!_audioTrack)) {
+ close();
+ return false;
+ }
+
+ // Rewind back to the beginning
+ _stream->seek(0);
+
+ return true;
+}
+
+void Scalpel3DOMovieDecoder::close() {
+ Video::VideoDecoder::close();
+
+ delete _stream; _stream = 0;
+ _videoTrack = 0;
+}
+
+// We try to at least decode 1 frame
+// and also try to get at least 0.5 seconds of audio queued up
+void Scalpel3DOMovieDecoder::readNextPacket() {
+ uint32 currentMovieTime = getTime();
+ uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
+
+ int32 chunkOffset = 0;
+ int32 dataStartOffset = 0;
+ int32 nextChunkOffset = 0;
+ uint32 chunkTag = 0;
+ uint32 chunkSize = 0;
+
+ uint32 videoSubType = 0;
+ uint32 videoTimeStamp = 0;
+ uint32 videoFrameSize = 0;
+ uint32 audioSubType = 0;
+ uint32 audioBytes = 0;
+ bool videoGotFrame = false;
+ bool videoDone = false;
+ bool audioDone = false;
+
+ // Seek to smallest stream offset
+ if (_streamVideoOffset <= _streamAudioOffset) {
+ _stream->seek(_streamVideoOffset);
+ } else {
+ _stream->seek(_streamAudioOffset);
+ }
+
+ if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+ // already got enough audio queued up
+ audioDone = true;
+ }
+
+ while (1) {
+ chunkOffset = _stream->pos();
+ assert(chunkOffset >= 0);
+
+ // Read chunk header
+ chunkTag = _stream->readUint32BE();
+ chunkSize = _stream->readUint32BE() - 8;
+
+ // Calculate offsets
+ dataStartOffset = _stream->pos();
+ assert(dataStartOffset >= 0);
+ nextChunkOffset = dataStartOffset + chunkSize;
+
+ //warning("offset %lx - tag %lx", dataStartOffset, tag);
+
+ if (_stream->eos())
+ break;
+
+ switch (chunkTag) {
+ case MKTAG('F','I','L','M'):
+ videoTimeStamp = _stream->readUint32BE();
+ _stream->skip(4); // Unknown
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // Ignore video header
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ // Found frame data
+ if (_streamVideoOffset <= chunkOffset) {
+ // We are at an offset that is still relevant to video decoding
+ if (!videoDone) {
+ if (!videoGotFrame) {
+ // We haven't decoded any frame yet, so do so now
+ _stream->readUint32BE();
+ videoFrameSize = _stream->readUint32BE();
+ _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp);
+
+ _streamVideoOffset = nextChunkOffset;
+ videoGotFrame = true;
+
+ } else {
+ // Already decoded a frame, so get timestamp of follow-up frame
+ // and then we are done with video
+
+ // Calculate next frame time
+ // 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240.
+ uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime();
+ uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240;
+ assert(currentFrameStartTime <= nextFrameStartTime);
+ _videoTrack->setNextFrameStartTime(nextFrameStartTime);
+
+ // next time we want to start at the current chunk
+ _streamVideoOffset = chunkOffset;
+ videoDone = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ break;
+ }
+ break;
+
+ case MKTAG('S','N','D','S'):
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Ignore the audio header
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Got audio chunk
+ if (_streamAudioOffset <= chunkOffset) {
+ // We are at an offset that is still relevant to audio decoding
+ if (!audioDone) {
+ audioBytes = _stream->readUint32BE();
+ _audioTrack->queueAudio(_stream, audioBytes);
+
+ _streamAudioOffset = nextChunkOffset;
+ if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+ // Got enough audio
+ audioDone = true;
+ }
+ }
+ }
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside SNDS packet");
+ break;
+ }
+ break;
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary
+ case MKTAG('D','A','C','Q'):
+ case MKTAG('J','O','I','N'): // add cel data (not used in sherlock)
+ // Ignore these chunks
+ break;
+
+ case MKTAG('S','H','D','R'):
+ // Happens for EA logo, seems to be garbage data right at the start of the file
+ break;
+
+ default:
+ error("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+ }
+
+ // Always seek to end of chunk
+ // Sometimes not all of the chunk is filled with audio
+ _stream->seek(nextChunkOffset);
+
+ if ((videoDone) && (audioDone)) {
+ return;
+ }
+ }
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) {
+ _width = width;
+ _height = height;
+ _frameCount = frameCount;
+ _curFrame = -1;
+ _nextFrameStartTime = 0;
+
+ // Create the Cinepak decoder, if we're using it
+ if (codecTag == MKTAG('c', 'v', 'i', 'd'))
+ _codec = new Image::CinepakDecoder();
+ else
+ error("Unsupported Sherlock 3DO movie video codec tag '%s'", tag2str(codecTag));
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
+ delete _codec;
+}
+
+bool Scalpel3DOMovieDecoder::StreamVideoTrack::endOfTrack() const {
+ return getCurFrame() >= getFrameCount() - 1;
+}
+
+Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
+ return _codec->getPixelFormat();
+}
+
+void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) {
+ _surface = _codec->decodeFrame(*stream);
+ _curFrame++;
+}
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) {
+ switch (codecTag) {
+ case MKTAG('A','D','P','4'):
+ case MKTAG('S','D','X','2'):
+ // ADP4 + SDX2 are both allowed
+ break;
+
+ default:
+ error("Unsupported Sherlock 3DO movie audio codec tag '%s'", tag2str(codecTag));
+ }
+
+ _totalAudioQueued = 0; // currently 0 milliseconds queued
+
+ _codecTag = codecTag;
+ _sampleRate = sampleRate;
+ switch (channels) {
+ case 1:
+ _stereo = false;
+ break;
+ case 2:
+ _stereo = true;
+ break;
+ default:
+ error("Unsupported Sherlock 3DO movie audio channels %d", channels);
+ }
+
+ _audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
+
+ // reset audio decoder persistent spaces
+ memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace));
+ memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace));
+}
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
+ delete _audioStream;
+// free(_ADP4_PersistentSpace);
+// free(_SDX2_PersistentSpace);
+}
+
+void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
+ Common::SeekableReadStream *compressedAudioStream = 0;
+ Audio::RewindableAudioStream *audioStream = 0;
+ uint32 audioLengthMSecs = 0;
+
+ // Read the specified chunk into memory
+ compressedAudioStream = stream->readStream(size);
+
+ switch(_codecTag) {
+ case MKTAG('A','D','P','4'):
+ audioStream = Audio::make3DO_ADP4AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_ADP4_PersistentSpace);
+ break;
+ case MKTAG('S','D','X','2'):
+ audioStream = Audio::make3DO_SDX2AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_SDX2_PersistentSpace);
+ break;
+ default:
+ break;
+ }
+ if (audioStream) {
+ _totalAudioQueued += audioLengthMSecs;
+ _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
+ } else {
+ // in case there was an error
+ delete compressedAudioStream;
+ }
+}
+
+Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
+
+// Test-code
+
+// Code for showing a movie. Only meant for testing/debug purposes
+bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos) {
+ Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder();
+
+ if (!videoDecoder->loadFile(filename)) {
+ warning("Scalpel3DOMoviePlay: could not open '%s'", filename);
+ return false;
+ }
+
+ bool skipVideo = false;
+ //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel;
+ uint16 width = videoDecoder->getWidth();
+ uint16 height = videoDecoder->getHeight();
+ //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel;
+
+ videoDecoder->start();
+
+ while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && (!skipVideo)) {
+ if (videoDecoder->needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+ if (frame) {
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, pos.x, pos.y, width, height);
+ g_system->updateScreen();
+ }
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE))
+ skipVideo = true;
+ }
+
+ g_system->delayMillis(10);
+ }
+ videoDecoder->close();
+ delete videoDecoder;
+
+ return !skipVideo;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h
new file mode 100644
index 0000000000..9f1670fc6c
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.h
@@ -0,0 +1,127 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+
+#include "common/rect.h"
+#include "video/video_decoder.h"
+#include "audio/decoders/3do.h"
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+class Codec;
+}
+
+namespace Sherlock {
+
+class Scalpel3DOMovieDecoder : public Video::VideoDecoder {
+public:
+ Scalpel3DOMovieDecoder();
+ ~Scalpel3DOMovieDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+protected:
+ void readNextPacket();
+
+private:
+ int32 _streamVideoOffset; /* current stream offset for video decoding */
+ int32 _streamAudioOffset; /* current stream offset for audio decoding */
+
+private:
+ class StreamVideoTrack : public VideoTrack {
+ public:
+ StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount);
+ ~StreamVideoTrack();
+
+ bool endOfTrack() const;
+
+ uint16 getWidth() const { return _width; }
+ uint16 getHeight() const { return _height; }
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime; }
+ const Graphics::Surface *decodeNextFrame() { return _surface; }
+
+ void decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp);
+
+ private:
+ const Graphics::Surface *_surface;
+
+ int _curFrame;
+ uint32 _frameCount;
+ uint32 _nextFrameStartTime;
+
+ Image::Codec *_codec;
+ uint16 _width, _height;
+ };
+
+ class StreamAudioTrack : public AudioTrack {
+ public:
+ StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels);
+ ~StreamAudioTrack();
+
+ void queueAudio(Common::SeekableReadStream *stream, uint32 size);
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audioStream;
+ uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */
+
+ public:
+ uint32 getTotalAudioQueued() const { return _totalAudioQueued; }
+
+ private:
+ int16 decodeSample(uint8 dataNibble);
+
+ uint32 _codecTag;
+ uint16 _sampleRate;
+ bool _stereo;
+
+ Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace;
+ Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace;
+ };
+
+ Common::SeekableReadStream *_stream;
+ StreamVideoTrack *_videoTrack;
+ StreamAudioTrack *_audioTrack;
+};
+
+// Testing
+extern bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos);
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/darts.cpp b/engines/sherlock/scalpel/darts.cpp
new file mode 100644
index 0000000000..a24af4e444
--- /dev/null
+++ b/engines/sherlock/scalpel/darts.cpp
@@ -0,0 +1,553 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/darts.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum {
+ STATUS_INFO_X = 218,
+ STATUS_INFO_Y = 53,
+ DART_INFO_X = 218,
+ DART_INFO_Y = 103,
+ DARTBARHX = 35,
+ DARTHORIZY = 190,
+ DARTBARVX = 1,
+ DARTHEIGHTY = 25,
+ DARTBARSIZE = 150,
+ DART_BAR_FORE = 8
+};
+
+enum {
+ DART_COL_FORE = 5,
+ PLAYER_COLOR = 11
+};
+#define OPPONENTS_COUNT 4
+
+const char *const OPPONENT_NAMES[OPPONENTS_COUNT] = {
+ "Skipper", "Willy", "Micky", "Tom"
+};
+
+/*----------------------------------------------------------------*/
+
+Darts::Darts(ScalpelEngine *vm) : _vm(vm) {
+ _dartImages = nullptr;
+ _level = 0;
+ _computerPlayer = 1;
+ _playerDartMode = false;
+ _dartScore1 = _dartScore2 = 0;
+ _roundNumber = 0;
+ _playerDartMode = false;
+ _roundScore = 0;
+ _oldDartButtons = false;
+}
+
+void Darts::playDarts() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int playerNumber = 0;
+ int lastDart;
+
+ // Change the font
+ int oldFont = screen.fontNumber();
+ screen.setFont(2);
+
+ loadDarts();
+ initDarts();
+
+ bool done = false;
+ do {
+ int score, roundStartScore;
+ roundStartScore = score = playerNumber == 0 ? _dartScore1 : _dartScore2;
+
+ // Show player details
+ showNames(playerNumber);
+ showStatus(playerNumber);
+ _roundScore = 0;
+
+ if (_vm->shouldQuit())
+ return;
+
+ for (int idx = 0; idx < 3; ++idx) {
+ // Throw a single dart
+ if (_computerPlayer == 1)
+ lastDart = throwDart(idx + 1, playerNumber * 2);
+ else if (_computerPlayer == 2)
+ lastDart = throwDart(idx + 1, playerNumber + 1);
+ else
+ lastDart = throwDart(idx + 1, 0);
+
+ score -= lastDart;
+ _roundScore += lastDart;
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
+ Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", idx + 1);
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Scored %d points", lastDart);
+
+ if (score != 0 && playerNumber == 0)
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), DART_COL_FORE, "Press a key");
+
+ if (score == 0) {
+ // Some-one has won
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "GAME OVER!");
+
+ if (playerNumber == 0) {
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "Holmes Wins!");
+ if (_level < OPPONENTS_COUNT)
+ _vm->setFlagsDirect(318 + _level);
+ } else {
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "%s Wins!", _opponent.c_str());
+ }
+
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 4), DART_COL_FORE, "Press a key");
+
+ idx = 10;
+ done = true;
+ } else if (score < 0) {
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "BUSTED!");
+
+ idx = 10;
+ score = roundStartScore;
+ }
+
+ if (playerNumber == 0)
+ _dartScore1 = score;
+ else
+ _dartScore2 = score;
+
+ showStatus(playerNumber);
+ events.clearKeyboard();
+
+ if ((playerNumber == 0 && _computerPlayer == 1) || _computerPlayer == 0 || done) {
+ int dartKey;
+ while (!(dartKey = dartHit()) && !_vm->shouldQuit())
+ events.delay(10);
+
+ if (dartKey == Common::KEYCODE_ESCAPE) {
+ idx = 10;
+ done = true;
+ }
+ } else {
+ events.wait(20);
+ }
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
+ Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ playerNumber ^= 1;
+ if (!playerNumber)
+ ++_roundNumber;
+
+ done |= _vm->shouldQuit();
+
+ if (!done) {
+ screen._backBuffer2.blitFrom((*_dartImages)[0], Common::Point(0, 0));
+ screen._backBuffer1.blitFrom(screen._backBuffer2);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+ } while (!done);
+
+ closeDarts();
+ screen.fadeToBlack();
+
+ // Restore font
+ screen.setFont(oldFont);
+}
+
+void Darts::loadDarts() {
+ Screen &screen = *_vm->_screen;
+
+ _dartImages = new ImageFile("darts.vgs");
+ screen.setPalette(_dartImages->_palette);
+
+ screen._backBuffer1.blitFrom((*_dartImages)[0], Common::Point(0, 0));
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+}
+
+void Darts::initDarts() {
+ _dartScore1 = _dartScore2 = 301;
+ _roundNumber = 1;
+
+ if (_level == 9) {
+ // No computer players
+ _computerPlayer = 0;
+ _level = 0;
+ } else if (_level == 8) {
+ _level = _vm->getRandomNumber(3);
+ _computerPlayer = 2;
+ } else {
+ // Check flags for opponents
+ for (int idx = 0; idx < OPPONENTS_COUNT; ++idx) {
+ if (_vm->readFlags(314 + idx))
+ _level = idx;
+ }
+ }
+
+ _opponent = OPPONENT_NAMES[_level];
+}
+
+void Darts::closeDarts() {
+ delete _dartImages;
+ _dartImages = nullptr;
+}
+
+void Darts::showNames(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ byte color = playerNum == 0 ? PLAYER_COLOR : DART_COL_FORE;
+
+ // Print Holmes first
+ if (playerNum == 0)
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), PLAYER_COLOR + 3, "Holmes");
+ else
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), color, "Holmes");
+
+ screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10,
+ STATUS_INFO_X + 31, STATUS_INFO_Y + 12), color);
+ screen.slamArea(STATUS_INFO_X, STATUS_INFO_Y + 10, 31, 12);
+
+ // Second player
+ color = playerNum == 1 ? PLAYER_COLOR : DART_COL_FORE;
+
+ if (playerNum != 0)
+ screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), PLAYER_COLOR + 3,
+ "%s", _opponent.c_str());
+ else
+ screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), color,
+ "%s", _opponent.c_str());
+
+ screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X + 50, STATUS_INFO_Y + 10,
+ STATUS_INFO_X + 81, STATUS_INFO_Y + 12), color);
+ screen.slamArea(STATUS_INFO_X + 50, STATUS_INFO_Y + 10, 81, 12);
+
+ // Make a copy of the back buffer to the secondary one
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+}
+
+void Darts::showStatus(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ byte color;
+
+ // Copy scoring screen from secondary back buffer. This will erase any previously displayed status/score info
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
+ Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48));
+
+ color = (playerNum == 0) ? PLAYER_COLOR : DART_COL_FORE;
+ screen.print(Common::Point(STATUS_INFO_X + 6, STATUS_INFO_Y + 13), color, "%d", _dartScore1);
+
+ color = (playerNum == 1) ? PLAYER_COLOR : DART_COL_FORE;
+ screen.print(Common::Point(STATUS_INFO_X + 56, STATUS_INFO_Y + 13), color, "%d", _dartScore2);
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 25), PLAYER_COLOR, "Round: %d", _roundNumber);
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 35), PLAYER_COLOR, "Turn Total: %d", _roundScore);
+ screen.slamRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48));
+}
+
+int Darts::throwDart(int dartNum, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point targetNum;
+ int width, height;
+
+ events.clearKeyboard();
+
+ erasePowerBars();
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", dartNum);
+
+ if (!computer) {
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Hit a key");
+ screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 18), DART_COL_FORE, "to start");
+ }
+
+ if (!computer) {
+ while (!_vm->shouldQuit() && !dartHit())
+ ;
+ } else {
+ events.delay(10);
+ }
+
+ if (_vm->shouldQuit())
+ return 0;
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1),
+ Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.slamRect(Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ // If it's a computer player, choose a dart destination
+ if (computer)
+ targetNum = getComputerDartDest(computer - 1);
+
+ width = doPowerBar(Common::Point(DARTBARHX, DARTHORIZY), DART_BAR_FORE, targetNum.x, false);
+ height = 101 - doPowerBar(Common::Point(DARTBARVX, DARTHEIGHTY), DART_BAR_FORE, targetNum.y, true);
+
+ // For human players, slight y adjustment
+ if (computer == 0)
+ height += 2;
+
+ // Copy the bars to the secondary back buffer so that they remain fixed at their selected values
+ // whilst the dart is being animated at being thrown at the board
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARHX - 1, DARTHORIZY - 1),
+ Common::Rect(DARTBARHX - 1, DARTHORIZY - 1, DARTBARHX + DARTBARSIZE + 3, DARTHORIZY + 10));
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1),
+ Common::Rect(DARTBARVX - 1, DARTHEIGHTY - 1, DARTBARVX + 11, DARTHEIGHTY + DARTBARSIZE + 3));
+
+ // Convert height and width to relative range of -50 to 50, where 0,0 is the exact centre of the board
+ height -= 50;
+ width -= 50;
+
+ Common::Point dartPos(111 + width * 2, 99 + height * 2);
+ drawDartThrow(dartPos);
+
+ return dartScore(dartPos);
+}
+
+void Darts::drawDartThrow(const Common::Point &pt) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point pos(pt.x, pt.y + 2);
+ Common::Rect oldDrawBounds;
+ int delta = 9;
+
+ for (int idx = 4; idx < 23; ++idx) {
+ ImageFrame &frame = (*_dartImages)[idx];
+
+ // Adjust draw position for animating dart
+ if (idx < 13)
+ pos.y -= delta--;
+ else if (idx == 13)
+ delta = 1;
+ else
+ pos.y += delta++;
+
+ // Draw the dart
+ Common::Point drawPos(pos.x - frame._width / 2, pos.y - frame._height);
+ screen._backBuffer1.transBlitFrom(frame, drawPos);
+ screen.slamArea(drawPos.x, drawPos.y, frame._width, frame._height);
+
+ // Handle erasing old dart frame area
+ if (!oldDrawBounds.isEmpty())
+ screen.slamRect(oldDrawBounds);
+
+ oldDrawBounds = Common::Rect(drawPos.x, drawPos.y, drawPos.x + frame._width, drawPos.y + frame._height);
+ screen._backBuffer1.blitFrom(screen._backBuffer2, drawPos, oldDrawBounds);
+
+ events.wait(2);
+ }
+
+ // Draw dart in final "stuck to board" form
+ screen._backBuffer1.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top));
+ screen._backBuffer2.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top));
+ screen.slamRect(oldDrawBounds);
+}
+
+void Darts::erasePowerBars() {
+ Screen &screen = *_vm->_screen;
+
+ screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK);
+ screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK);
+ screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1));
+ screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1));
+ screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11);
+ screen.slamArea(DARTBARVX - 1, DARTHEIGHTY - 1, 11, DARTBARSIZE + 3);
+}
+
+int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Music &music = *_vm->_music;
+ bool done;
+ int idx = 0;
+
+ events.clearEvents();
+ if (music._musicOn)
+ music.waitTimerRoland(10);
+ else
+ events.delay(100);
+
+ // Display loop
+ do {
+ done = _vm->shouldQuit() || idx >= DARTBARSIZE;
+
+ if (idx == (goToPower - 1))
+ // Reached target power for a computer player
+ done = true;
+ else if (goToPower == 0) {
+ // Check for pres
+ if (dartHit())
+ done = true;
+ }
+
+ if (isVertical) {
+ screen._backBuffer1.hLine(pt.x, pt.y + DARTBARSIZE - 1 - idx, pt.x + 8, color);
+ screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(pt.x - 1, pt.y - 1));
+ screen.slamArea(pt.x, pt.y + DARTBARSIZE - 1 - idx, 8, 2);
+ } else {
+ screen._backBuffer1.vLine(pt.x + idx, pt.y, pt.y + 8, color);
+ screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(pt.x - 1, pt.y - 1));
+ screen.slamArea(pt.x + idx, pt.y, 1, 8);
+ }
+
+ if (music._musicOn) {
+ if (!(idx % 3))
+ music.waitTimerRoland(1);
+ } else if (!(idx % 8))
+ events.wait(1);
+
+ ++idx;
+ } while (!done);
+
+ return MIN(idx * 100 / DARTBARSIZE, 100);
+}
+
+bool Darts::dartHit() {
+ Events &events = *_vm->_events;
+
+ // Process pending events
+ events.pollEventsAndWait();
+
+ if (events.kbHit()) {
+ // Key was pressed, so discard it and return true
+ events.clearKeyboard();
+ return true;
+ }
+
+ _oldDartButtons = events._pressed;
+ events.setButtonState();
+
+ // Only return true if the mouse button is newly pressed
+ return (events._pressed && !_oldDartButtons) ? 1 : 0;
+}
+
+int Darts::dartScore(const Common::Point &pt) {
+ Common::Point pos(pt.x - 37, pt.y - 33);
+ Graphics::Surface &scoreImg = (*_dartImages)[1]._frame;
+
+ if (pos.x < 0 || pos.y < 0 || pos.x >= scoreImg.w || pos.y >= scoreImg.h)
+ // Not on the board
+ return 0;
+
+ // On board, so get the score from the pixel at that position
+ int score = *(const byte *)scoreImg.getBasePtr(pos.x, pos.y);
+ return score;
+}
+
+Common::Point Darts::getComputerDartDest(int playerNum) {
+ Common::Point target;
+ int score = playerNum == 0 ? _dartScore1 : _dartScore2;
+
+ if (score > 50) {
+ // Aim for the bullseye
+ target.x = target.y = 76;
+
+ if (_level <= 1 && _vm->getRandomNumber(1) == 1) {
+ // Introduce margin of error
+ target.x += _vm->getRandomNumber(21) - 10;
+ target.y += _vm->getRandomNumber(21) - 10;
+ }
+ } else {
+ int aim = score;
+
+ bool done;
+ Common::Point pt;
+ do {
+ done = findNumberOnBoard(aim, pt);
+ --aim;
+ } while (!done);
+
+ target.x = 75 + ((target.x - 75) * 20 / 27);
+ target.y = 75 + ((target.y - 75) * 2 / 3);
+ }
+
+ // Pick a level of accuracy. The higher the level, the more accurate their throw will be
+ int accuracy = _vm->getRandomNumber(10) + _level * 2;
+
+ if (accuracy <= 2) {
+ target.x += _vm->getRandomNumber(71) - 35;
+ target.y += _vm->getRandomNumber(71) - 35;
+ } else if (accuracy <= 4) {
+ target.x += _vm->getRandomNumber(51) - 25;
+ target.y += _vm->getRandomNumber(51) - 25;
+ } else if (accuracy <= 6) {
+ target.x += _vm->getRandomNumber(31) - 15;
+ target.y += _vm->getRandomNumber(31) - 15;
+ } else if (accuracy <= 8) {
+ target.x += _vm->getRandomNumber(21) - 10;
+ target.y += _vm->getRandomNumber(21) - 10;
+ } else if (accuracy <= 10) {
+ target.x += _vm->getRandomNumber(11) - 5;
+ target.y += _vm->getRandomNumber(11) - 5;
+ }
+
+ if (target.x < 1)
+ target.x = 1;
+ if (target.y < 1)
+ target.y = 1;
+
+ return target;
+}
+
+bool Darts::findNumberOnBoard(int aim, Common::Point &pt) {
+ ImageFrame &board = (*_dartImages)[1];
+
+ // Scan board image for the special "center" pixels
+ bool done = false;
+ for (int yp = 0; yp < 132 && !done; ++yp) {
+ const byte *srcP = (const byte *)board._frame.getBasePtr(0, yp);
+ for (int xp = 0; xp < 147 && !done; ++xp, ++srcP) {
+ int score = *srcP;
+
+ // Check for match
+ if (score == aim) {
+ done = true;
+
+ // Aim at non-double/triple numbers where possible
+ if (aim < 21) {
+ pt.x = xp + 5;
+ pt.y = yp + 5;
+
+ score = *(const byte *)board._frame.getBasePtr(xp + 10, yp + 10);
+ if (score != aim)
+ // Not aiming at non-double/triple number yet
+ done = false;
+ } else {
+ // Aiming at a double or triple
+ pt.x = xp + 3;
+ pt.y = yp + 3;
+ }
+ }
+ }
+ }
+
+ if (aim == 3)
+ pt.x += 15;
+ pt.y = 132 - pt.y;
+
+ return done;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/darts.h b/engines/sherlock/scalpel/darts.h
new file mode 100644
index 0000000000..4368954814
--- /dev/null
+++ b/engines/sherlock/scalpel/darts.h
@@ -0,0 +1,130 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_DARTS_H
+#define SHERLOCK_DARTS_H
+
+#include "sherlock/image_file.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+class ScalpelEngine;
+
+class Darts {
+private:
+ ScalpelEngine *_vm;
+ ImageFile *_dartImages;
+ int _dartScore1, _dartScore2;
+ int _roundNumber;
+ int _level;
+ int _computerPlayer;
+ Common::String _opponent;
+ bool _playerDartMode;
+ int _roundScore;
+ bool _oldDartButtons;
+
+ /**
+ * Load the graphics needed for the dart game
+ */
+ void loadDarts();
+
+ /**
+ * Initializes the variables needed for the dart game
+ */
+ void initDarts();
+
+ /**
+ * Frees the images used by the dart game
+ */
+ void closeDarts();
+
+ /**
+ * Show the names of the people playing, Holmes and his opponent
+ */
+ void showNames(int playerNum);
+
+ /**
+ * Show the player score and game status
+ */
+ void showStatus(int playerNum);
+
+ /**
+ * Throws a single dart.
+ * @param dartNum Dart number
+ * @param computer 0 = Player, 1 = 1st player computer, 2 = 2nd player computer
+ * @returns Score for what dart hit
+ */
+ int throwDart(int dartNum, int computer);
+
+ /**
+ * Draw a dart moving towards the board
+ */
+ void drawDartThrow(const Common::Point &pt);
+
+ /**
+ * Erases the power bars
+ */
+ void erasePowerBars();
+
+ /**
+ * Show a gradually incrementing incrementing power that bar. If goToPower is provided, it will
+ * increment to that power level ignoring all keyboard input (ie. for computer throws).
+ * Otherwise, it will increment until either a key/mouse button is pressed, or it reaches the end
+ */
+ int doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical);
+
+ /**
+ * Returns true if a mouse button or key is pressed.
+ */
+ bool dartHit();
+
+ /**
+ * Return the score of the given location on the dart-board
+ */
+ int dartScore(const Common::Point &pt);
+
+ /**
+ * Calculates where a computer player is trying to throw their dart, and choose the actual
+ * point that was hit with some margin of error
+ */
+ Common::Point getComputerDartDest(int playerNum);
+
+ /**
+ * Returns the center position for the area of the dartboard with a given number
+ */
+ bool findNumberOnBoard(int aim, Common::Point &pt);
+public:
+ Darts(ScalpelEngine *vm);
+
+ /**
+ * Main method for playing darts game
+ */
+ void playDarts();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/drivers/adlib.cpp b/engines/sherlock/scalpel/drivers/adlib.cpp
new file mode 100644
index 0000000000..29a39f0c39
--- /dev/null
+++ b/engines/sherlock/scalpel/drivers/adlib.cpp
@@ -0,0 +1,646 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/drivers/mididriver.h"
+
+#include "common/file.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "audio/fmopl.h"
+#include "audio/softsynth/emumidi.h"
+
+namespace Sherlock {
+
+#define SHERLOCK_ADLIB_VOICES_COUNT 9
+#define SHERLOCK_ADLIB_NOTES_COUNT 96
+
+byte operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
+};
+
+byte operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = {
+ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15
+};
+
+struct percussionChannelEntry {
+ byte requiredNote;
+ byte replacementNote;
+};
+
+// hardcoded, dumped from ADHOM.DRV
+const percussionChannelEntry percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = {
+ { 0x00, 0x00 },
+ { 0x00, 0x00 },
+ { 0x00, 0x00 },
+ { 0x00, 0x00 },
+ { 0x00, 0x00 },
+ { 0x00, 0x00 },
+ { 0x24, 0x0C },
+ { 0x38, 0x01 },
+ { 0x26, 0x1E }
+};
+
+struct InstrumentEntry {
+ byte reg20op1;
+ byte reg40op1;
+ byte reg60op1;
+ byte reg80op1;
+ byte regE0op1;
+ byte reg20op2;
+ byte reg40op2;
+ byte reg60op2;
+ byte reg80op2;
+ byte regE0op2;
+ byte regC0;
+ byte frequencyAdjust;
+};
+
+// hardcoded, dumped from ADHOM.DRV
+const InstrumentEntry instrumentTable[] = {
+ { 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 },
+ { 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 },
+ { 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 },
+ { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xE8 },
+ { 0x71, 0x8B, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x35, 0x01, 0x02, 0xF4 },
+ { 0x71, 0x8A, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x25, 0x01, 0x02, 0xF4 },
+ { 0x23, 0x0F, 0xF4, 0x04, 0x02, 0x2F, 0x25, 0xF0, 0x43, 0x00, 0x06, 0xE8 },
+ { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x8A, 0x6E, 0x17, 0x00, 0x25, 0x27, 0x6B, 0x0E, 0x00, 0x02, 0xF4 },
+ { 0x71, 0x1D, 0x81, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x17, 0x00, 0x0E, 0xF4 },
+ { 0x01, 0x4B, 0xF1, 0x50, 0x00, 0x01, 0x23, 0xD2, 0x76, 0x00, 0x06, 0xF4 },
+ { 0x2F, 0xCA, 0xF8, 0xE5, 0x00, 0x21, 0x1F, 0xC0, 0xFF, 0x00, 0x00, 0xF4 },
+ { 0x29, 0xCD, 0xF0, 0x91, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
+ { 0x24, 0xD0, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
+ { 0x23, 0xC8, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 },
+ { 0x64, 0xC9, 0xB0, 0x01, 0x00, 0x61, 0x1F, 0xF0, 0x86, 0x00, 0x02, 0xF4 },
+ { 0x33, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x72, 0x23, 0x00, 0x08, 0xF4 },
+ { 0x31, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 },
+ { 0x31, 0x81, 0xA1, 0x30, 0x00, 0x16, 0x9F, 0xC2, 0x74, 0x00, 0x08, 0xF4 },
+ { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x02, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
+ { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
+ { 0x23, 0x8A, 0xF2, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 },
+ { 0x32, 0x80, 0x01, 0x10, 0x00, 0x12, 0x9F, 0x72, 0x33, 0x00, 0x08, 0xF4 },
+ { 0x32, 0x80, 0x01, 0x10, 0x00, 0x14, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 },
+ { 0x31, 0x16, 0x73, 0x8E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0xF4 },
+ { 0x30, 0x16, 0x73, 0x7E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0x00 },
+ { 0x31, 0x94, 0x33, 0x73, 0x00, 0x21, 0x1F, 0xA0, 0x97, 0x00, 0x0E, 0xF4 },
+ { 0x31, 0x94, 0xD3, 0x73, 0x00, 0x21, 0x20, 0xA0, 0x97, 0x00, 0x0E, 0xF4 },
+ { 0x31, 0x45, 0xF1, 0x53, 0x00, 0x32, 0x1F, 0xF2, 0x27, 0x00, 0x06, 0xF4 },
+ { 0x13, 0x0C, 0xF2, 0x01, 0x00, 0x15, 0x2F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 },
+ { 0x11, 0x0C, 0xF2, 0x01, 0x00, 0x11, 0x1F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 },
+ { 0x11, 0x0A, 0xFE, 0x04, 0x00, 0x11, 0x1F, 0xF2, 0xBD, 0x00, 0x08, 0xF4 },
+ { 0x16, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 },
+ { 0x16, 0x40, 0xBA, 0x11, 0x00, 0xF1, 0x20, 0x24, 0x31, 0x00, 0x08, 0xF4 },
+ { 0x61, 0xA7, 0x72, 0x8E, 0x00, 0xE1, 0x9F, 0x50, 0x1A, 0x00, 0x02, 0xF4 },
+ { 0x18, 0x4D, 0x32, 0x13, 0x00, 0xE1, 0x20, 0x51, 0xE3, 0x00, 0x08, 0xF4 },
+ { 0x17, 0xC0, 0x12, 0x41, 0x00, 0x31, 0x9F, 0x13, 0x31, 0x00, 0x06, 0xF4 },
+ { 0x03, 0x8F, 0xF5, 0x55, 0x00, 0x21, 0x9F, 0xF3, 0x33, 0x00, 0x00, 0xF4 },
+ { 0x13, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 },
+ { 0x11, 0x43, 0x20, 0x15, 0x00, 0xF1, 0x20, 0x31, 0xF8, 0x00, 0x08, 0xF4 },
+ { 0x11, 0x03, 0x82, 0x97, 0x00, 0xE4, 0x60, 0xF0, 0xF2, 0x00, 0x08, 0xF4 },
+ { 0x05, 0x40, 0xD1, 0x53, 0x00, 0x14, 0x1F, 0x51, 0x71, 0x00, 0x06, 0xF4 },
+ { 0xF1, 0x01, 0x77, 0x17, 0x00, 0x21, 0x1F, 0x81, 0x18, 0x00, 0x02, 0xF4 },
+ { 0xF1, 0x18, 0x32, 0x11, 0x00, 0xE1, 0x1F, 0xF1, 0x13, 0x00, 0x00, 0xF4 },
+ { 0x73, 0x48, 0xF1, 0x53, 0x00, 0x71, 0x1F, 0xF1, 0x06, 0x00, 0x08, 0xF4 },
+ { 0x71, 0x8D, 0x71, 0x11, 0x00, 0x61, 0x5F, 0x72, 0x15, 0x00, 0x06, 0xF4 },
+ { 0xD7, 0x4F, 0xF2, 0x61, 0x00, 0xD2, 0x1F, 0xF1, 0xB2, 0x00, 0x08, 0xF4 },
+ { 0x01, 0x11, 0xF0, 0xFF, 0x00, 0x01, 0x1F, 0xF0, 0xF8, 0x00, 0x0A, 0xF4 },
+ { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x22, 0x13, 0x00, 0x06, 0xF4 },
+ { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x07, 0x00, 0x0E, 0xF4 },
+ { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x32, 0x15, 0x00, 0x02, 0xF4 },
+ { 0x71, 0x1C, 0xFD, 0x13, 0x00, 0x21, 0x1F, 0xE7, 0xD6, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x67, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x1C, 0x54, 0x15, 0x00, 0x21, 0x1F, 0x53, 0x49, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x56, 0x51, 0x03, 0x00, 0x61, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
+ { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 },
+ { 0x02, 0x29, 0xF5, 0x75, 0x00, 0x01, 0x9F, 0xF2, 0xF3, 0x00, 0x00, 0xF4 },
+ { 0x02, 0x29, 0xF0, 0x75, 0x00, 0x01, 0x9F, 0xF4, 0x33, 0x00, 0x00, 0xF4 },
+ { 0x01, 0x49, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
+ { 0x01, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
+ { 0x02, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
+ { 0x02, 0x80, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 },
+ { 0x01, 0x40, 0xF1, 0x53, 0x00, 0x08, 0x5F, 0xF1, 0x53, 0x00, 0x00, 0xF4 },
+ { 0x21, 0x15, 0xD3, 0x2C, 0x00, 0x21, 0x9F, 0xC3, 0x2C, 0x00, 0x0A, 0xF4 },
+ { 0x01, 0x18, 0xD4, 0xF2, 0x00, 0x21, 0x9F, 0xC4, 0x8A, 0x00, 0x0A, 0xF4 },
+ { 0x01, 0x4E, 0xF0, 0x7B, 0x00, 0x11, 0x1F, 0xF4, 0xC8, 0x00, 0x04, 0xF4 },
+ { 0x01, 0x44, 0xF0, 0xAB, 0x00, 0x11, 0x1F, 0xF3, 0xAB, 0x00, 0x04, 0xF4 },
+ { 0x53, 0x0E, 0xF4, 0xC8, 0x00, 0x11, 0x1F, 0xF1, 0xBB, 0x00, 0x04, 0xF4 },
+ { 0x53, 0x0B, 0xF2, 0xC8, 0x00, 0x11, 0x1F, 0xF2, 0xC5, 0x00, 0x04, 0xF4 },
+ { 0x21, 0x15, 0xB4, 0x4C, 0x00, 0x21, 0x1F, 0x94, 0xAC, 0x00, 0x0A, 0xF4 },
+ { 0x21, 0x15, 0x94, 0x1C, 0x00, 0x21, 0x1F, 0x64, 0xAC, 0x00, 0x0A, 0xF4 },
+ { 0x22, 0x1B, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 },
+ { 0x21, 0x19, 0x77, 0xBF, 0x00, 0xA1, 0x9F, 0x60, 0x2A, 0x00, 0x06, 0xF4 },
+ { 0xA1, 0x13, 0xD6, 0xAF, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
+ { 0xA2, 0x1D, 0x95, 0x24, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
+ { 0x32, 0x9A, 0x51, 0x19, 0x00, 0x61, 0x9F, 0x60, 0x39, 0x00, 0x0C, 0xF4 },
+ { 0xA4, 0x12, 0xF4, 0x30, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 },
+ { 0x21, 0x16, 0x63, 0x0E, 0x00, 0x21, 0x1F, 0x63, 0x0E, 0x00, 0x0C, 0xF4 },
+ { 0x31, 0x16, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
+ { 0x21, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
+ { 0x20, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 },
+ { 0x32, 0x1C, 0x82, 0x18, 0x00, 0x61, 0x9F, 0x60, 0x07, 0x00, 0x0C, 0xF4 },
+ { 0x32, 0x18, 0x61, 0x14, 0x00, 0xE1, 0x9F, 0x72, 0x16, 0x00, 0x0C, 0xF4 },
+ { 0x31, 0xC0, 0x77, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x09, 0x00, 0x02, 0xF4 },
+ { 0x71, 0xC3, 0x8E, 0x17, 0x00, 0x22, 0x24, 0x8B, 0x0E, 0x00, 0x02, 0xF4 },
+ { 0x70, 0x8D, 0x6E, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x0E, 0x00, 0x02, 0xF4 },
+ { 0x24, 0x4F, 0xF2, 0x06, 0x00, 0x31, 0x1F, 0x52, 0x06, 0x00, 0x0E, 0xF4 },
+ { 0x31, 0x1B, 0x64, 0x07, 0x00, 0x61, 0x1F, 0xD0, 0x67, 0x00, 0x0E, 0xF4 },
+ { 0x31, 0x1B, 0x61, 0x06, 0x00, 0x61, 0x1F, 0xD2, 0x36, 0x00, 0x0C, 0xF4 },
+ { 0x31, 0x1F, 0x31, 0x06, 0x00, 0x61, 0x1F, 0x50, 0x36, 0x00, 0x0C, 0xF4 },
+ { 0x31, 0x1F, 0x41, 0x06, 0x00, 0x61, 0x1F, 0xA0, 0x36, 0x00, 0x0C, 0xF4 },
+ { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 },
+ { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 },
+ { 0x61, 0x19, 0x53, 0x58, 0x00, 0x21, 0x1F, 0xA0, 0x18, 0x00, 0x0C, 0xF4 },
+ { 0x61, 0x19, 0x73, 0x57, 0x00, 0x21, 0x1F, 0xA0, 0x17, 0x00, 0x0C, 0xF4 },
+ { 0x21, 0x1B, 0x71, 0xA6, 0x00, 0x21, 0x1F, 0xA1, 0x96, 0x00, 0x0E, 0xF4 },
+ { 0x85, 0x91, 0xF5, 0x44, 0x00, 0xA1, 0x1F, 0xF0, 0x45, 0x00, 0x06, 0xF4 },
+ { 0x07, 0x51, 0xF5, 0x33, 0x00, 0x61, 0x1F, 0xF0, 0x25, 0x00, 0x06, 0xF4 },
+ { 0x13, 0x8C, 0xFF, 0x21, 0x00, 0x11, 0x9F, 0xFF, 0x03, 0x00, 0x0E, 0xF4 },
+ { 0x38, 0x8C, 0xF3, 0x0D, 0x00, 0xB1, 0x5F, 0xF5, 0x33, 0x00, 0x0E, 0xF4 },
+ { 0x87, 0x91, 0xF5, 0x55, 0x00, 0x22, 0x1F, 0xF0, 0x54, 0x00, 0x06, 0xF4 },
+ { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xF4 },
+ { 0x04, 0x00, 0xFE, 0xF0, 0x00, 0xC2, 0x1F, 0xF6, 0xB5, 0x00, 0x0E, 0xF4 },
+ { 0x05, 0x4E, 0xDA, 0x15, 0x00, 0x01, 0x9F, 0xF0, 0x13, 0x00, 0x0A, 0xF4 },
+ { 0x31, 0x44, 0xF2, 0x9A, 0x00, 0x32, 0x1F, 0xF0, 0x27, 0x00, 0x06, 0xF4 },
+ { 0xB0, 0xC4, 0xA4, 0x02, 0x00, 0xD7, 0x9F, 0x40, 0x42, 0x00, 0x00, 0xF4 },
+ { 0xCA, 0x84, 0xF0, 0xF0, 0x00, 0xCF, 0x1F, 0x59, 0x62, 0x00, 0x0C, 0xF4 },
+ { 0x30, 0x35, 0xF5, 0xF0, 0x00, 0x35, 0x1F, 0xF0, 0x9B, 0x00, 0x02, 0xF4 },
+ { 0x63, 0x0F, 0xF4, 0x04, 0x02, 0x6F, 0x1F, 0xF0, 0x43, 0x00, 0x06, 0xF4 },
+ { 0x07, 0x40, 0x09, 0x53, 0x00, 0x05, 0x1F, 0xF6, 0x94, 0x00, 0x0E, 0xF4 },
+ { 0x09, 0x4E, 0xDA, 0x25, 0x00, 0x01, 0x1F, 0xF1, 0x15, 0x00, 0x0A, 0xF4 },
+ { 0x04, 0x00, 0xF3, 0xA0, 0x02, 0x04, 0x1F, 0xF8, 0x46, 0x00, 0x0E, 0xF4 },
+ { 0x07, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x1F, 0x5C, 0xDC, 0x00, 0x0E, 0xF4 },
+ { 0x1F, 0x1E, 0xE5, 0x5B, 0x00, 0x0F, 0x1F, 0x5D, 0xFA, 0x00, 0x0E, 0xF4 },
+ { 0x11, 0x8A, 0xF1, 0x11, 0x00, 0x01, 0x5F, 0xF1, 0xB3, 0x00, 0x06, 0xF4 },
+ { 0x00, 0x40, 0xD1, 0x53, 0x00, 0x00, 0x1F, 0xF2, 0x56, 0x00, 0x0E, 0xF4 },
+ { 0x32, 0x44, 0xF8, 0xFF, 0x00, 0x11, 0x1F, 0xF5, 0x7F, 0x00, 0x0E, 0xF4 },
+ { 0x00, 0x40, 0x09, 0x53, 0x00, 0x02, 0x1F, 0xF7, 0x94, 0x00, 0x0E, 0xF4 },
+ { 0x11, 0x86, 0xF2, 0xA8, 0x00, 0x01, 0x9F, 0xA0, 0xA8, 0x00, 0x08, 0xF4 },
+ { 0x00, 0x50, 0xF2, 0x70, 0x00, 0x13, 0x1F, 0xF2, 0x72, 0x00, 0x0E, 0xF4 },
+ { 0xF0, 0x00, 0x11, 0x11, 0x00, 0xE0, 0xDF, 0x11, 0x11, 0x00, 0x0E, 0xF4 }
+};
+
+// hardcoded, dumped from ADHOM.DRV
+uint16 frequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = {
+ 0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242,
+ 0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603,
+ 0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB,
+ 0x09E6, 0x0A03, 0x0A22, 0x0A42, 0x0A65, 0x0A89, 0x0D58, 0x0D6C, 0x0D82, 0x0D99,
+ 0x0DB1, 0x0DCB, 0x0DE6, 0x0E03, 0x0E22, 0x0E42, 0x0E65, 0x0E89, 0x1158, 0x116C,
+ 0x1182, 0x1199, 0x11B1, 0x11CB, 0x11E6, 0x1203, 0x1222, 0x1242, 0x1265, 0x1289,
+ 0x1558, 0x156C, 0x1582, 0x1599, 0x15B1, 0x15CB, 0x15E6, 0x1603, 0x1622, 0x1642,
+ 0x1665, 0x1689, 0x1958, 0x196C, 0x1982, 0x1999, 0x19B1, 0x19CB, 0x19E6, 0x1A03,
+ 0x1A22, 0x1A42, 0x1A65, 0x1A89, 0x1D58, 0x1D6C, 0x1D82, 0x1D99, 0x1DB1, 0x1DCB,
+ 0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89
+};
+
+class MidiDriver_SH_AdLib : public MidiDriver {
+public:
+ MidiDriver_SH_AdLib(Audio::Mixer *mixer)
+ : _masterVolume(15), _opl(0),
+ _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) {
+ memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping));
+ }
+ virtual ~MidiDriver_SH_AdLib() { }
+
+ // MidiDriver
+ int open();
+ void close();
+ void send(uint32 b);
+ MidiChannel *allocateChannel() { return NULL; }
+ MidiChannel *getPercussionChannel() { return NULL; }
+ bool isOpen() const { return _isOpen; }
+ uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
+
+ int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; }
+ bool hasRhythmChannel() const { return false; }
+
+ virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
+
+ void setVolume(byte volume);
+ virtual uint32 property(int prop, uint32 param);
+
+ void newMusicData(byte *musicData, int32 musicDataSize);
+
+private:
+ struct adlib_ChannelEntry {
+ bool inUse;
+ uint16 inUseTimer;
+ const InstrumentEntry *currentInstrumentPtr;
+ byte currentNote;
+ byte currentA0hReg;
+ byte currentB0hReg;
+
+ adlib_ChannelEntry() : inUse(false), inUseTimer(0), currentInstrumentPtr(NULL), currentNote(0),
+ currentA0hReg(0), currentB0hReg(0) { }
+ };
+
+ OPL::OPL *_opl;
+ int _masterVolume;
+
+ Common::TimerManager::TimerProc _adlibTimerProc;
+ void *_adlibTimerParam;
+
+ bool _isOpen;
+
+ // points to a MIDI channel for each of the new voice channels
+ byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT];
+
+ // stores information about all FM voice channels
+ adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT];
+
+ void onTimer();
+
+ void resetAdLib();
+ void resetAdLibOperatorRegisters(byte baseRegister, byte value);
+ void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
+
+ void programChange(byte MIDIchannel, byte parameter);
+ void setRegister(int reg, int value);
+ void noteOn(byte MIDIchannel, byte note, byte velocity);
+ void noteOff(byte MIDIchannel, byte note);
+ void voiceOnOff(byte FMVoiceChannel, bool KeyOn, byte note, byte velocity);
+
+ void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
+};
+
+int MidiDriver_SH_AdLib::open() {
+ debugC(kDebugLevelAdLibDriver, "AdLib: starting driver");
+
+ _opl = OPL::Config::create(OPL::Config::kOpl2);
+
+ if (!_opl)
+ return -1;
+
+ _opl->init();
+
+ _isOpen = true;
+
+ _opl->start(new Common::Functor0Mem<void, MidiDriver_SH_AdLib>(this, &MidiDriver_SH_AdLib::onTimer));
+
+ return 0;
+}
+
+void MidiDriver_SH_AdLib::close() {
+ // Stop the OPL timer
+ _opl->stop();
+
+ delete _opl;
+}
+
+void MidiDriver_SH_AdLib::setVolume(byte volume) {
+ _masterVolume = volume;
+ //renewNotes(-1, true);
+}
+
+// this should/must get called per tick
+// original driver did this before MIDI data processing on each tick
+// we do it atm after MIDI data processing
+void MidiDriver_SH_AdLib::onTimer() {
+ if (_adlibTimerProc)
+ (*_adlibTimerProc)(_adlibTimerParam);
+
+ // this should/must get called per tick
+ // original driver did this before MIDI data processing on each tick
+ // we do it atm after MIDI data processing
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_channels[FMvoiceChannel].inUse) {
+ _channels[FMvoiceChannel].inUseTimer++;
+ }
+ }
+}
+
+// Called when a music track got loaded into memory
+void MidiDriver_SH_AdLib::newMusicData(byte *musicData, int32 musicDataSize) {
+ assert(musicDataSize >= 0x7F);
+ // MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data
+ memcpy(&_voiceChannelMapping, musicData + 0x22, 9);
+
+ // reset OPL
+ resetAdLib();
+
+ // reset current channel data
+ memset(&_channels, 0, sizeof(_channels));
+}
+
+void MidiDriver_SH_AdLib::resetAdLib() {
+
+ setRegister(0x01, 0x20); // enable waveform control on both operators
+ setRegister(0x04, 0xE0); // Timer control
+
+ setRegister(0x08, 0); // select FM music mode
+ setRegister(0xBD, 0); // disable Rhythm
+
+ // reset FM voice instrument data
+ resetAdLibOperatorRegisters(0x20, 0);
+ resetAdLibOperatorRegisters(0x60, 0);
+ resetAdLibOperatorRegisters(0x80, 0);
+ resetAdLibFMVoiceChannelRegisters(0xA0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xB0, 0);
+ resetAdLibFMVoiceChannelRegisters(0xC0, 0);
+ resetAdLibOperatorRegisters(0xE0, 0);
+ resetAdLibOperatorRegisters(0x40, 0x3F);
+}
+
+void MidiDriver_SH_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
+ byte operatorIndex;
+
+ for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) {
+ switch (operatorIndex) {
+ case 0x06:
+ case 0x07:
+ case 0x0E:
+ case 0x0F:
+ break;
+ default:
+ setRegister(baseRegister + operatorIndex, value);
+ }
+ }
+}
+
+void MidiDriver_SH_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
+ byte FMvoiceChannel;
+
+ for (FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ setRegister(baseRegister + FMvoiceChannel, value);
+ }
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_SH_AdLib::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+ byte op1 = (b >> 8) & 0xff;
+ byte op2 = (b >> 16) & 0xff;
+
+ switch (command) {
+ case 0x80:
+ noteOff(channel, op1);
+ break;
+ case 0x90:
+ noteOn(channel, op1, op2);
+ break;
+ case 0xb0: // Control change
+ // Doesn't seem to be implemented in the Sherlock Holmes adlib driver
+ break;
+ case 0xc0: // Program Change
+ programChange(channel, op1);
+ break;
+ case 0xa0: // Polyphonic key pressure (aftertouch)
+ case 0xd0: // Channel pressure (aftertouch)
+ // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver
+ break;
+ case 0xe0:
+ debugC(kDebugLevelAdLibDriver, "AdLib: pitch bend change");
+ pitchBendChange(channel, op1, op2);
+ break;
+ case 0xf0: // SysEx
+ warning("ADLIB: SysEx: %x", b);
+ break;
+ default:
+ warning("ADLIB: Unknown event %02x", command);
+ }
+}
+
+void MidiDriver_SH_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) {
+ int16 oldestInUseChannel = -1;
+ uint16 oldestInUseTimer = 0;
+
+ if (velocity == 0)
+ return noteOff(MIDIchannel, note);
+
+ if (MIDIchannel != 9) {
+ // Not Percussion
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+ if (!_channels[FMvoiceChannel].inUse) {
+ _channels[FMvoiceChannel].inUse = true;
+ _channels[FMvoiceChannel].currentNote = note;
+
+ voiceOnOff(FMvoiceChannel, true, note, velocity);
+ return;
+ }
+ }
+ }
+
+ // Look for oldest in-use channel
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+ if (_channels[FMvoiceChannel].inUseTimer > oldestInUseTimer) {
+ oldestInUseTimer = _channels[FMvoiceChannel].inUseTimer;
+ oldestInUseChannel = FMvoiceChannel;
+ }
+ }
+ }
+ if (oldestInUseChannel >= 0) {
+ // channel found
+ debugC(kDebugLevelAdLibDriver, "AdLib: used In-Use channel");
+ // original driver used note 0, we use the current note
+ // because using note 0 could create a bad note (out of index) and we check that. Original driver didn't.
+ voiceOnOff(oldestInUseChannel, false, _channels[oldestInUseChannel].currentNote, 0);
+
+ _channels[oldestInUseChannel].inUse = true;
+ _channels[oldestInUseChannel].inUseTimer = 0; // safety, original driver also did this
+ _channels[oldestInUseChannel].currentNote = note;
+ voiceOnOff(oldestInUseChannel, true, note, velocity);
+ return;
+ }
+ debugC(kDebugLevelAdLibDriver, "AdLib: MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel);
+
+ } else {
+ // Percussion channel
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+ if (note == percussionChannelTable[FMvoiceChannel].requiredNote) {
+ _channels[FMvoiceChannel].inUse = true;
+ _channels[FMvoiceChannel].currentNote = note;
+
+ voiceOnOff(FMvoiceChannel, true, percussionChannelTable[FMvoiceChannel].replacementNote, velocity);
+ return;
+ }
+ }
+ }
+ debugC(kDebugLevelAdLibDriver, "AdLib: percussion MIDI channel not mapped/all FM voice channels busy");
+ }
+}
+
+void MidiDriver_SH_AdLib::noteOff(byte MIDIchannel, byte note) {
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+ if (_channels[FMvoiceChannel].currentNote == note) {
+ _channels[FMvoiceChannel].inUse = false;
+ _channels[FMvoiceChannel].inUseTimer = 0;
+ _channels[FMvoiceChannel].currentNote = 0;
+
+ if (MIDIchannel != 9) {
+ // not-percussion
+ voiceOnOff(FMvoiceChannel, false, note, 0);
+ } else {
+ voiceOnOff(FMvoiceChannel, false, percussionChannelTable[FMvoiceChannel].replacementNote, 0);
+ }
+ return;
+ }
+ }
+ }
+}
+
+void MidiDriver_SH_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) {
+ byte frequencyOffset = 0;
+ uint16 frequency = 0;
+ byte op2RegAdjust = 0;
+ byte regValue40h = 0;
+ byte regValueA0h = 0;
+ byte regValueB0h = 0;
+
+ // Look up frequency
+ if (_channels[FMvoiceChannel].currentInstrumentPtr) {
+ frequencyOffset = note + _channels[FMvoiceChannel].currentInstrumentPtr->frequencyAdjust;
+ } else {
+ frequencyOffset = note;
+ }
+ if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) {
+ warning("CRITICAL - AdLib driver: bad note!!!");
+ return;
+ }
+ frequency = frequencyLookUpTable[frequencyOffset];
+
+ if (keyOn) {
+ // adjust register 40h
+ if (_channels[FMvoiceChannel].currentInstrumentPtr) {
+ regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2;
+ }
+ regValue40h = regValue40h - (velocity >> 3);
+ op2RegAdjust = operator2Register[FMvoiceChannel];
+ setRegister(0x40 + op2RegAdjust, regValue40h);
+ }
+
+ regValueA0h = frequency & 0xFF;
+ regValueB0h = frequency >> 8;
+ if (keyOn) {
+ regValueB0h |= 0x20; // set Key-On flag
+ }
+
+ setRegister(0xA0 + FMvoiceChannel, regValueA0h);
+ setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+ _channels[FMvoiceChannel].currentA0hReg = regValueA0h;
+ _channels[FMvoiceChannel].currentB0hReg = regValueB0h;
+}
+
+void MidiDriver_SH_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) {
+ uint16 channelFrequency = 0;
+ byte channelRegB0hWithoutFrequency = 0;
+ uint16 parameter = 0;
+ byte regValueA0h = 0;
+ byte regValueB0h = 0;
+
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+ if (_channels[FMvoiceChannel].inUse) {
+ // FM voice channel found and it's currently in use -> apply pitch bend change
+
+ // Remove frequency bits from current channel B0h-register
+ channelFrequency = ((_channels[FMvoiceChannel].currentB0hReg << 8) | (_channels[FMvoiceChannel].currentA0hReg)) & 0x3FF;
+ channelRegB0hWithoutFrequency = _channels[FMvoiceChannel].currentB0hReg & 0xFC;
+
+ if (parameter2 < 0x40) {
+ channelFrequency = channelFrequency / 2;
+ } else {
+ parameter2 = parameter2 - 0x40;
+ }
+ parameter1 = parameter1 * 2;
+ parameter = parameter1 | (parameter2 << 8);
+ parameter = parameter * 4;
+
+ parameter = (parameter >> 8) + 0xFF;
+ channelFrequency = channelFrequency * parameter;
+ channelFrequency = (channelFrequency >> 8) | (parameter << 8);
+
+ regValueA0h = channelFrequency & 0xFF;
+ regValueB0h = (channelFrequency >> 8) | channelRegB0hWithoutFrequency;
+
+ setRegister(0xA0 + FMvoiceChannel, regValueA0h);
+ setRegister(0xB0 + FMvoiceChannel, regValueB0h);
+ }
+ }
+ }
+}
+
+void MidiDriver_SH_AdLib::programChange(byte MIDIchannel, byte op1) {
+ const InstrumentEntry *instrumentPtr;
+ byte op1Reg = 0;
+ byte op2Reg = 0;
+
+ // setup instrument
+ instrumentPtr = &instrumentTable[op1];
+ //warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1);
+
+ for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) {
+ if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) {
+
+ op1Reg = operator1Register[FMvoiceChannel];
+ op2Reg = operator2Register[FMvoiceChannel];
+
+ setRegister(0x20 + op1Reg, instrumentPtr->reg20op1);
+ setRegister(0x40 + op1Reg, instrumentPtr->reg40op1);
+ setRegister(0x60 + op1Reg, instrumentPtr->reg60op1);
+ setRegister(0x80 + op1Reg, instrumentPtr->reg80op1);
+ setRegister(0xE0 + op1Reg, instrumentPtr->regE0op1);
+
+ setRegister(0x20 + op2Reg, instrumentPtr->reg20op2);
+ setRegister(0x40 + op2Reg, instrumentPtr->reg40op2);
+ setRegister(0x60 + op2Reg, instrumentPtr->reg60op2);
+ setRegister(0x80 + op2Reg, instrumentPtr->reg80op2);
+ setRegister(0xE0 + op2Reg, instrumentPtr->regE0op2);
+
+ setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0);
+
+ // Remember instrument
+ _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr;
+ }
+ }
+}
+void MidiDriver_SH_AdLib::setRegister(int reg, int value) {
+ _opl->write(0x220, reg);
+ _opl->write(0x221, value);
+}
+
+uint32 MidiDriver_SH_AdLib::property(int prop, uint32 param) {
+ return 0;
+}
+
+void MidiDriver_SH_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
+ _adlibTimerProc = timerProc;
+ _adlibTimerParam = timerParam;
+}
+
+MidiDriver *MidiDriver_SH_AdLib_create() {
+ return new MidiDriver_SH_AdLib(g_system->getMixer());
+}
+
+void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
+ static_cast<MidiDriver_SH_AdLib *>(driver)->newMusicData(musicData, musicDataSize);
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/drivers/mididriver.h b/engines/sherlock/scalpel/drivers/mididriver.h
new file mode 100644
index 0000000000..1b8ceeda3d
--- /dev/null
+++ b/engines/sherlock/scalpel/drivers/mididriver.h
@@ -0,0 +1,41 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
+#define SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
+
+#include "sherlock/sherlock.h"
+#include "audio/mididrv.h"
+#include "common/error.h"
+
+namespace Sherlock {
+
+extern MidiDriver *MidiDriver_SH_AdLib_create();
+extern void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
+
+extern MidiDriver *MidiDriver_MT32_create();
+extern void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize);
+extern void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize);
+
+} // End of namespace Sherlock
+
+#endif // SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H
diff --git a/engines/sherlock/scalpel/drivers/mt32.cpp b/engines/sherlock/scalpel/drivers/mt32.cpp
new file mode 100644
index 0000000000..33e7671719
--- /dev/null
+++ b/engines/sherlock/scalpel/drivers/mt32.cpp
@@ -0,0 +1,282 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/drivers/mididriver.h"
+
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+//#include "audio/mididrv.h"
+
+namespace Sherlock {
+
+#define SHERLOCK_MT32_CHANNEL_COUNT 16
+
+const byte mt32ReverbDataSysEx[] = {
+ 0x10, 0x00, 0x01, 0x01, 0x05, 0x05, 0xFF
+};
+
+class MidiDriver_MT32 : public MidiDriver {
+public:
+ MidiDriver_MT32() {
+ _driver = NULL;
+ _isOpen = false;
+ _nativeMT32 = false;
+ _baseFreq = 250;
+
+ memset(_MIDIchannelActive, 1, sizeof(_MIDIchannelActive));
+ }
+ virtual ~MidiDriver_MT32();
+
+ // MidiDriver
+ int open();
+ void close();
+ bool isOpen() const { return _isOpen; }
+
+ void send(uint32 b);
+
+ void newMusicData(byte *musicData, int32 musicDataSize);
+
+ MidiChannel *allocateChannel() {
+ if (_driver)
+ return _driver->allocateChannel();
+ return NULL;
+ }
+ MidiChannel *getPercussionChannel() {
+ if (_driver)
+ return _driver->getPercussionChannel();
+ return NULL;
+ }
+
+ void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
+ if (_driver)
+ _driver->setTimerCallback(timer_param, timer_proc);
+ }
+
+ uint32 getBaseTempo() {
+ if (_driver) {
+ return _driver->getBaseTempo();
+ }
+ return 1000000 / _baseFreq;
+ }
+
+protected:
+ Common::Mutex _mutex;
+ MidiDriver *_driver;
+ bool _nativeMT32;
+
+ bool _isOpen;
+ int _baseFreq;
+
+private:
+ // points to a MIDI channel for each of the new voice channels
+ byte _MIDIchannelActive[SHERLOCK_MT32_CHANNEL_COUNT];
+
+public:
+ void uploadMT32Patches(byte *driverData, int32 driverSize);
+
+ void mt32SysEx(const byte *&dataPtr, int32 &bytesLeft);
+};
+
+MidiDriver_MT32::~MidiDriver_MT32() {
+ Common::StackLock lock(_mutex);
+ if (_driver) {
+ _driver->setTimerCallback(0, 0);
+ _driver->close();
+ delete _driver;
+ }
+ _driver = NULL;
+}
+
+int MidiDriver_MT32::open() {
+ assert(!_driver);
+
+ debugC(kDebugLevelMT32Driver, "MT32: starting driver");
+
+ // Setup midi driver
+ MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
+ MusicType musicType = MidiDriver::getMusicType(dev);
+
+ switch (musicType) {
+ case MT_MT32:
+ _nativeMT32 = true;
+ break;
+ case MT_GM:
+ if (ConfMan.getBool("native_mt32")) {
+ _nativeMT32 = true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ _driver = MidiDriver::createMidi(dev);
+ if (!_driver)
+ return 255;
+
+ if (_nativeMT32)
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ if (_nativeMT32)
+ _driver->sendMT32Reset();
+ else
+ _driver->sendGMReset();
+
+ return 0;
+}
+
+void MidiDriver_MT32::close() {
+ if (_driver) {
+ _driver->close();
+ }
+}
+
+// Called when a music track got loaded into memory
+void MidiDriver_MT32::newMusicData(byte *musicData, int32 musicDataSize) {
+ assert(musicDataSize >= 0x7F); // Security check
+
+ // MIDI Channel Enable/Disable bytes at offset 0x2 of music data
+ memcpy(&_MIDIchannelActive, musicData + 0x2, SHERLOCK_MT32_CHANNEL_COUNT);
+
+ // Send 16 bytes from offset 0x12 to MT32
+ // All the music tracks of Sherlock seem to contain dummy data
+ // probably a feature, that was used in the game "Ski or Die"
+ // that's why we don't implement this
+
+ // Also send these bytes to MT32 (SysEx) - seems to be reverb configuration
+ if (_nativeMT32) {
+ const byte *reverbData = mt32ReverbDataSysEx;
+ int32 reverbDataSize = sizeof(mt32ReverbDataSysEx);
+ mt32SysEx(reverbData, reverbDataSize);
+ }
+}
+
+void MidiDriver_MT32::uploadMT32Patches(byte *driverData, int32 driverSize) {
+ if (!_driver)
+ return;
+
+ if (!_nativeMT32)
+ return;
+
+ // patch data starts at offset 0x863
+ assert(driverSize == 0x13B9); // Security check
+ assert(driverData[0x863] == 0x7F); // another security check
+
+ const byte *patchPtr = driverData + 0x863;
+ int32 bytesLeft = driverSize - 0x863;
+
+ while(1) {
+ mt32SysEx(patchPtr, bytesLeft);
+
+ assert(bytesLeft);
+ if (*patchPtr == 0x80) // List terminator
+ break;
+ }
+}
+
+void MidiDriver_MT32::mt32SysEx(const byte *&dataPtr, int32 &bytesLeft) {
+ byte sysExMessage[270];
+ uint16 sysExPos = 0;
+ byte sysExByte = 0;
+ uint16 sysExChecksum = 0;
+
+ memset(&sysExMessage, 0, sizeof(sysExMessage));
+
+ sysExMessage[0] = 0x41; // Roland
+ sysExMessage[1] = 0x10;
+ sysExMessage[2] = 0x16; // Model MT32
+ sysExMessage[3] = 0x12; // Command DT1
+
+ sysExPos = 4;
+ sysExChecksum = 0;
+ while (1) {
+ assert(bytesLeft);
+
+ sysExByte = *dataPtr++;
+ bytesLeft--;
+ if (sysExByte == 0xff)
+ break; // Message done
+
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExByte;
+ sysExChecksum -= sysExByte;
+ }
+
+ // Calculate checksum
+ assert(sysExPos < sizeof(sysExMessage));
+ sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
+
+ debugC(kDebugLevelMT32Driver, "MT32: uploading patch data, size %d", sysExPos);
+
+ // Send SysEx
+ _driver->sysEx(sysExMessage, sysExPos);
+
+ // Wait the time it takes to send the SysEx data
+ uint32 delay = (sysExPos + 2) * 1000 / 3125;
+
+ // Plus an additional delay for the MT-32 rev00
+ if (_nativeMT32)
+ delay += 40;
+
+ g_system->delayMillis(delay);
+}
+
+// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
+void MidiDriver_MT32::send(uint32 b) {
+ byte command = b & 0xf0;
+ byte channel = b & 0xf;
+
+ if (command == 0xF0) {
+ if (_driver) {
+ _driver->send(b);
+ }
+ return;
+ }
+
+ if (_MIDIchannelActive[channel]) {
+ // Only forward MIDI-data in case the channel is currently enabled via music-data
+ if (_driver) {
+ _driver->send(b);
+ }
+ }
+}
+
+MidiDriver *MidiDriver_MT32_create() {
+ return new MidiDriver_MT32();
+}
+
+void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) {
+ static_cast<MidiDriver_MT32 *>(driver)->newMusicData(musicData, musicDataSize);
+}
+
+void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize) {
+ static_cast<MidiDriver_MT32 *>(driver)->uploadMT32Patches(driverData, driverSize);
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp
new file mode 100644
index 0000000000..af9d613cce
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel.cpp
@@ -0,0 +1,1160 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/util.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/scalpel/tsage/logo.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/music.h"
+#include "sherlock/animation.h"
+// for 3DO
+#include "sherlock/scalpel/3do/movie_decoder.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+#define PROLOGUE_NAMES_COUNT 6
+
+// The following are a list of filenames played in the prologue that have
+// special effects associated with them at specific frames
+static const char *const PROLOGUE_NAMES[PROLOGUE_NAMES_COUNT] = {
+ "subway1", "subway2", "finale2", "suicid", "coff3", "coff4"
+};
+
+static const int PROLOGUE_FRAMES[6][9] = {
+ { 4, 26, 54, 72, 92, 134, FRAMES_END },
+ { 2, 80, 95, 117, 166, FRAMES_END },
+ { 1, FRAMES_END },
+ { 42, FRAMES_END },
+ { FRAMES_END },
+ { FRAMES_END }
+};
+
+#define TITLE_NAMES_COUNT 7
+
+// Title animations file list
+static const char *const TITLE_NAMES[TITLE_NAMES_COUNT] = {
+ "27pro1", "14note", "coff1", "coff2", "coff3", "coff4", "14kick"
+};
+
+static const int TITLE_FRAMES[7][9] = {
+ { 29, 131, FRAMES_END },
+ { 55, 80, 95, 117, 166, FRAMES_END },
+ { 15, FRAMES_END },
+ { 4, 37, 92, FRAMES_END },
+ { 2, 43, FRAMES_END },
+ { 2, FRAMES_END },
+ { 10, 50, FRAMES_END }
+};
+
+#define NUM_PLACES 100
+
+static const int MAP_X[NUM_PLACES] = {
+ 0, 368, 0, 219, 0, 282, 0, 43, 0, 0, 396, 408, 0, 0, 0, 568, 37, 325,
+ 28, 0, 263, 36, 148, 469, 342, 143, 443, 229, 298, 0, 157, 260, 432,
+ 174, 0, 351, 0, 528, 0, 136, 0, 0, 0, 555, 165, 0, 506, 0, 0, 344, 0, 0
+};
+static const int MAP_Y[NUM_PLACES] = {
+ 0, 147, 0, 166, 0, 109, 0, 61, 0, 0, 264, 70, 0, 0, 0, 266, 341, 30, 275,
+ 0, 294, 146, 311, 230, 184, 268, 133, 94, 207, 0, 142, 142, 330, 255, 0,
+ 37, 0, 70, 0, 116, 0, 0, 0, 50, 21, 0, 303, 0, 0, 229, 0, 0
+};
+
+static const int MAP_TRANSLATE[NUM_PLACES] = {
+ 0, 0, 0, 1, 0, 2, 0, 3, 4, 0, 4, 6, 0, 0, 0, 8, 9, 10, 11, 0, 12, 13, 14, 7,
+ 15, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 24, 0, 25, 0, 26, 0, 0, 0, 27,
+ 28, 0, 29, 0, 0, 30, 0
+};
+
+static const byte MAP_SEQUENCES[3][MAX_FRAME] = {
+ { 1, 1, 2, 3, 4, 0 }, // Overview Still
+ { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 },
+ { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 }
+};
+
+#define MAX_PEOPLE 66
+
+struct PeopleData {
+ const char *portrait;
+ const char *name;
+ byte stillSequences[MAX_TALK_SEQUENCES];
+ byte talkSequences[MAX_TALK_SEQUENCES];
+};
+
+const PeopleData PEOPLE_DATA[MAX_PEOPLE] = {
+ { "HOLM", "Sherlock Holmes", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "WATS", "Dr. Watson", { 6, 0, 0 }, { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 } },
+ { "LEST", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } },
+ { "CON1", "Constable O'Brien", { 2, 0, 0 }, { 1, 0, 0 } },
+ { "CON2", "Constable Lewis", { 2, 0, 0 }, { 1, 0, 0 } },
+ { "SHEI", "Sheila Parker", { 2, 0, 0 }, { 2, 3, 0, 0 } },
+ { "HENR", "Henry Carruthers", { 3, 0, 0 }, { 3, 0, 0 } },
+ { "LESL", "Lesley", { 9, 0, 0 }, { 1, 2, 3, 2, 1, 2, 3, 0, 0 } },
+ { "USH1", "An Usher", { 13, 0, 0 }, { 13, 14, 0, 0 } },
+ { "USH2", "An Usher", { 2, 0, 0 }, { 2, 0, 0 } },
+ { "FRED", "Fredrick Epstein", { 4, 0, 0 }, { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 } },
+ { "WORT", "Mrs. Worthington", { 9, 0, 0 }, { 8, 0, 0 } },
+ { "COAC", "The Coach", { 2, 0, 0 }, { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 } },
+ { "PLAY", "A Player", { 8, 0, 0 }, { 7, 8, 0, 0 } },
+ { "WBOY", "Tim", { 13, 0, 0 }, { 12, 13, 0, 0 } },
+ { "JAME", "James Sanders", { 6, 0, 0 }, { 3, 4, 0, 0 } },
+ { "BELL", "Belle", { 1, 0, 0 }, { 4, 5, 0, 0 } },
+ { "GIRL", "Cleaning Girl", { 20, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 } },
+ { "EPST", "Fredrick Epstein", { 17, 0, 0 }, { 16, 17, 18, 18, 18, 17, 17, 0, 0 } },
+ { "WIGG", "Wiggins", { 3, 0, 0 }, { 2, 3, 0, 0 } },
+ { "PAUL", "Paul", { 2, 0, 0 }, { 1, 2, 0, 0 } },
+ { "BART", "The Bartender", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "DIRT", "A Dirty Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SHOU", "A Shouting Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "STAG", "A Staggering Drunk", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BOUN", "The Bouncer", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SAND", "James Sanders", { 6, 0, 0 }, { 5, 6, 0, 0 } },
+ { "CORO", "The Coroner", { 6, 0, 0 }, { 4, 5, 0, 0 } },
+ { "EQUE", "Reginald Snipes", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "GEOR", "George Blackwood", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "LARS", "Lars", { 7, 0, 0 }, { 5, 6, 0, 0 } },
+ { "PARK", "Sheila Parker", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "CHEM", "The Chemist", { 8, 0, 0 }, { 8, 9, 0, 0 } },
+ { "GREG", "Inspector Gregson", { 6, 0, 0 }, { 5, 6, 0, 0 } },
+ { "LAWY", "Jacob Farthington", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "MYCR", "Mycroft", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SHER", "Old Sherman", { 7, 0, 0 }, { 7, 8, 0, 0 } },
+ { "CHMB", "Richard", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BARM", "The Barman", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "DAND", "A Dandy Player", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "ROUG", "A Rough-looking Player", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "SPEC", "A Spectator", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "HUNT", "Robert Hunt", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "VIOL", "Violet", { 3, 0, 0 }, { 3, 4, 0, 0 } },
+ { "PETT", "Pettigrew", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "APPL", "Augie", { 8, 0, 0 }, { 14, 15, 0, 0 } },
+ { "ANNA", "Anna Carroway", { 16, 0, 0 }, { 3, 4, 5, 6, 0, 0 } },
+ { "GUAR", "A Guard", { 1, 0, 0 }, { 4, 5, 6, 0, 0 } },
+ { "ANTO", "Antonio Caruso", { 8, 0, 0 }, { 7, 8, 0, 0 } },
+ { "TOBY", "Toby the Dog", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "KING", "Simon Kingsley", { 13, 0, 0 }, { 13, 14, 0, 0 } },
+ { "ALFR", "Alfred", { 2, 0, 0 }, { 2, 3, 0, 0 } },
+ { "LADY", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } },
+ { "ROSA", "Madame Rosa", { 1, 0, 0 }, { 1, 30, 0, 0 } },
+ { "LADB", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } },
+ { "MOOR", "Joseph Moorehead", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "BEAL", "Mrs. Beale", { 5, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 0, 0 } },
+ { "LION", "Felix", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "HOLL", "Hollingston", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "CALL", "Constable Callaghan", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "JERE", "Sergeant Duncan", { 2, 0, 0 }, { 1, 1, 2, 2, 0, 0 } },
+ { "LORD", "Lord Brumwell", { 1, 0, 0 }, { 9, 10, 0, 0 } },
+ { "NIGE", "Nigel Jaimeson", { 1, 0, 0 }, { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 } },
+ { "JONA", "Jonas", { 1, 0, 0 }, { 1, 8, 0, 0 } },
+ { "DUGA", "Constable Dugan", { 1, 0, 0 }, { 1, 0, 0 } },
+ { "INSP", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } }
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelEngine::ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc) :
+ SherlockEngine(syst, gameDesc) {
+ _darts = nullptr;
+ _mapResult = 0;
+}
+
+ScalpelEngine::~ScalpelEngine() {
+ delete _darts;
+}
+
+void ScalpelEngine::initialize() {
+ // 3DO actually uses RGB555, but some platforms of ours only support RGB565, so we use that
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
+ // 320x200 16-bit RGB565 for 3DO support
+ initGraphics(320, 200, false, &pixelFormatRGB565);
+ } else {
+ // 320x200 palettized
+ initGraphics(320, 200, false);
+ }
+
+ // Let the base engine intialize
+ SherlockEngine::initialize();
+
+ _darts = new Darts(this);
+
+ _flags.resize(100 * 8);
+ _flags[3] = true; // Turn on Alley
+ _flags[39] = true; // Turn on Baker Street
+
+ if (!isDemo()) {
+ // Load the map co-ordinates for each scene and sequence data
+ ScalpelMap &map = *(ScalpelMap *)_map;
+ map.loadPoints(NUM_PLACES, &MAP_X[0], &MAP_Y[0], &MAP_TRANSLATE[0]);
+ map.loadSequences(3, &MAP_SEQUENCES[0][0]);
+ map._oldCharPoint = BAKER_ST_EXTERIOR;
+ }
+
+ // Load the inventory
+ loadInventory();
+
+ // Set up list of people
+ for (int idx = 0; idx < MAX_PEOPLE; ++idx)
+ _people->_characters.push_back(PersonData(PEOPLE_DATA[idx].name, PEOPLE_DATA[idx].portrait,
+ PEOPLE_DATA[idx].stillSequences, PEOPLE_DATA[idx].talkSequences));
+
+ _animation->setPrologueNames(&PROLOGUE_NAMES[0], PROLOGUE_NAMES_COUNT);
+ _animation->setPrologueFrames(&PROLOGUE_FRAMES[0][0], 6, 9);
+
+ _animation->setTitleNames(&TITLE_NAMES[0], TITLE_NAMES_COUNT);
+ _animation->setTitleFrames(&TITLE_FRAMES[0][0], 7, 9);
+
+ // Starting scene
+ if (isDemo() && _interactiveFl)
+ _scene->_goToScene = 3;
+ else
+ _scene->_goToScene = 4;
+}
+
+void ScalpelEngine::showOpening() {
+ bool finished = true;
+
+ if (isDemo() && _interactiveFl)
+ return;
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ show3DOSplash();
+
+ finished = showCityCutscene3DO();
+ if (finished)
+ finished = showAlleyCutscene3DO();
+ if (finished)
+ finished = showStreetCutscene3DO();
+ if (finished)
+ showOfficeCutscene3DO();
+
+ _events->clearEvents();
+ _music->stopMusic();
+ return;
+ }
+
+ TsAGE::Logo::show(this);
+ finished = showCityCutscene();
+ if (finished)
+ finished = showAlleyCutscene();
+ if (finished)
+ finished = showStreetCutscene();
+ if (finished)
+ showOfficeCutscene();
+
+ _events->clearEvents();
+ _music->stopMusic();
+}
+
+bool ScalpelEngine::showCityCutscene() {
+ byte greyPalette[PALETTE_SIZE];
+ byte palette[PALETTE_SIZE];
+
+ // Demo fades from black into grey and then fades from grey into the scene
+ Common::fill(&greyPalette[0], &greyPalette[PALETTE_SIZE], 142);
+ _screen->fadeIn((const byte *)greyPalette, 3);
+
+ _music->loadSong("prolog1");
+ _animation->_gfxLibraryFilename = "title.lib";
+ _animation->_soundLibraryFilename = "title.snd";
+ bool finished = _animation->play("26open1", true, 1, 255, true, 2);
+
+ if (finished) {
+ ImageFile titleImages_LondonNovember("title2.vgs", true);
+ _screen->_backBuffer1.blitFrom(*_screen);
+ _screen->_backBuffer2.blitFrom(*_screen);
+
+ Common::Point londonPosition;
+
+ if ((titleImages_LondonNovember[0]._width == 302) && (titleImages_LondonNovember[0]._height == 39)) {
+ // Spanish
+ londonPosition = Common::Point(9, 8);
+ } else {
+ // English (German uses the same English graphics), width 272, height 37
+ // In the German version this is placed differently, check against German floppy version TODO
+ londonPosition = Common::Point(30, 50);
+ }
+
+ // London, England
+ _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[0], londonPosition);
+ _screen->randomTransition();
+ finished = _events->delay(1000, true);
+
+ // November, 1888
+ if (finished) {
+ _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100));
+ _screen->randomTransition();
+ finished = _events->delay(5000, true);
+ }
+
+ // Transition out the title
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2);
+ _screen->randomTransition();
+ }
+
+ if (finished)
+ finished = _animation->play("26open2", true, 1, 0, false, 2);
+
+ if (finished) {
+ ImageFile titleImages_SherlockHolmesTitle("title.vgs", true);
+ _screen->_backBuffer1.blitFrom(*_screen);
+ _screen->_backBuffer2.blitFrom(*_screen);
+
+ Common::Point lostFilesPosition;
+ Common::Point sherlockHolmesPosition;
+ Common::Point copyrightPosition;
+
+ if ((titleImages_SherlockHolmesTitle[0]._width == 306) && (titleImages_SherlockHolmesTitle[0]._height == 39)) {
+ // Spanish
+ lostFilesPosition = Common::Point(5, 5);
+ sherlockHolmesPosition = Common::Point(24, 40);
+ copyrightPosition = Common::Point(3, 190);
+ } else {
+ // English (German uses the same English graphics), width 208, height 39
+ lostFilesPosition = Common::Point(75, 6);
+ sherlockHolmesPosition = Common::Point(34, 21);
+ copyrightPosition = Common::Point(4, 190);
+ }
+
+ // The Lost Files of
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition);
+ // Sherlock Holmes
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition);
+ // copyright
+ _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition);
+
+ _screen->verticalTransition();
+ finished = _events->delay(4000, true);
+
+ if (finished) {
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2);
+ _screen->randomTransition();
+ finished = _events->delay(2000);
+ }
+
+ if (finished) {
+ _screen->getPalette(palette);
+ _screen->fadeToBlack(2);
+ }
+
+ if (finished) {
+ // In the alley...
+ Common::Point alleyPosition;
+
+ if ((titleImages_SherlockHolmesTitle[3]._width == 105) && (titleImages_SherlockHolmesTitle[3]._height == 16)) {
+ // German
+ alleyPosition = Common::Point(72, 50);
+ } else if ((titleImages_SherlockHolmesTitle[3]._width == 166) && (titleImages_SherlockHolmesTitle[3]._height == 36)) {
+ // Spanish
+ alleyPosition = Common::Point(71, 50);
+ } else {
+ // English, width 175, height 38
+ alleyPosition = Common::Point(72, 51);
+ }
+ _screen->transBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition);
+ _screen->fadeIn(palette, 3);
+
+ // Wait until the track got looped and the first few notes were played
+ finished = _music->waitUntilMSec(4300, 21300, 0, 2500); // ticks 0x104 / ticks 0x500
+ }
+ }
+
+ _animation->_gfxLibraryFilename = "";
+ _animation->_soundLibraryFilename = "";
+ return finished;
+}
+
+bool ScalpelEngine::showAlleyCutscene() {
+ byte palette[PALETTE_SIZE];
+ _music->loadSong("prolog2");
+
+ _animation->_gfxLibraryFilename = "TITLE.LIB";
+ _animation->_soundLibraryFilename = "TITLE.SND";
+
+ // Fade "In The Alley..." text to black
+ _screen->fadeToBlack(2);
+
+ bool finished = _animation->play("27PRO1", true, 1, 3, true, 2);
+ if (finished) {
+ _screen->getPalette(palette);
+ _screen->fadeToBlack(2);
+
+ // wait until second lower main note
+ finished = _music->waitUntilMSec(26800, 0xFFFFFFFF, 0, 1000); // ticks 0x64A
+ }
+
+ if (finished) {
+ _screen->setPalette(palette);
+ finished = _animation->play("27PRO2", true, 1, 0, false, 2);
+ }
+
+ if (finished) {
+ showLBV("scream.lbv");
+
+ // wait until first "scream" in music happened
+ finished = _music->waitUntilMSec(45800, 0xFFFFFFFF, 0, 6000); // ticks 0xABE
+ }
+
+ if (finished) {
+ // quick fade out
+ _screen->fadeToBlack(1);
+
+ // wait until after third "scream" in music happened
+ finished = _music->waitUntilMSec(49000, 0xFFFFFFFF, 0, 2000); // ticks 0xB80
+ }
+
+ if (finished)
+ finished = _animation->play("27PRO3", true, 1, 0, true, 2);
+
+ if (finished) {
+ _screen->getPalette(palette);
+ _screen->fadeToBlack(2);
+ }
+
+ if (finished) {
+ ImageFile titleImages_EarlyTheFollowingMorning("title3.vgs", true);
+ // "Early the following morning on Baker Street..."
+ Common::Point earlyTheFollowingMorningPosition;
+
+ if ((titleImages_EarlyTheFollowingMorning[0]._width == 164) && (titleImages_EarlyTheFollowingMorning[0]._height == 19)) {
+ // German
+ earlyTheFollowingMorningPosition = Common::Point(35, 50);
+ } else if ((titleImages_EarlyTheFollowingMorning[0]._width == 171) && (titleImages_EarlyTheFollowingMorning[0]._height == 32)) {
+ // Spanish
+ earlyTheFollowingMorningPosition = Common::Point(35, 50);
+ } else {
+ // English, width 218, height 31
+ earlyTheFollowingMorningPosition = Common::Point(35, 52);
+ }
+
+ _screen->transBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition);
+
+ // fast fade-in
+ _screen->fadeIn(palette, 1);
+
+ // wait for music to end and wait an additional 2.5 seconds
+ finished = _music->waitUntilMSec(0xFFFFFFFF, 0xFFFFFFFF, 2500, 3000);
+ }
+
+ _animation->_gfxLibraryFilename = "";
+ _animation->_soundLibraryFilename = "";
+ return finished;
+}
+
+bool ScalpelEngine::showStreetCutscene() {
+ _animation->_gfxLibraryFilename = "TITLE.LIB";
+ _animation->_soundLibraryFilename = "TITLE.SND";
+
+ _music->loadSong("prolog3");
+
+ // wait a bit
+ bool finished = _events->delay(500);
+
+ if (finished) {
+ // fade out "Early the following morning..."
+ _screen->fadeToBlack(2);
+
+ // wait for music a bit
+ finished = _music->waitUntilMSec(3800, 0xFFFFFFFF, 0, 1000); // ticks 0xE4
+ }
+
+ if (finished)
+ finished = _animation->play("14KICK", true, 1, 3, true, 2);
+
+ // Constable animation plays slower than speed 2
+ // If we play it with speed 2, music gets obviously out of sync
+ if (finished)
+ finished = _animation->play("14NOTE", true, 1, 0, false, 3);
+
+ // Fade to black
+ if (finished)
+ _screen->fadeToBlack(1);
+
+ _animation->_gfxLibraryFilename = "";
+ _animation->_soundLibraryFilename = "";
+ return finished;
+}
+
+bool ScalpelEngine::showOfficeCutscene() {
+ _music->loadSong("prolog4");
+ _animation->_gfxLibraryFilename = "TITLE2.LIB";
+ _animation->_soundLibraryFilename = "TITLE.SND";
+
+ bool finished = _animation->play("COFF1", true, 1, 3, true, 3);
+ if (finished)
+ finished = _animation->play("COFF2", true, 1, 0, false, 3);
+ if (finished) {
+ showLBV("note.lbv");
+
+ if (_sound->_voices) {
+ finished = _sound->playSound("NOTE1", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE2", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE3", WAIT_KBD_OR_FINISH);
+ if (finished)
+ finished = _sound->playSound("NOTE4", WAIT_KBD_OR_FINISH);
+ } else
+ finished = _events->delay(19000);
+
+ if (finished) {
+ _events->clearEvents();
+ finished = _events->delay(500);
+ }
+ }
+
+ if (finished)
+ finished = _animation->play("COFF3", true, 1, 0, true, 3);
+
+ if (finished)
+ finished = _animation->play("COFF4", true, 1, 0, false, 3);
+
+ if (finished)
+ finished = scrollCredits();
+
+ if (finished)
+ _screen->fadeToBlack(3);
+
+ _animation->_gfxLibraryFilename = "";
+ _animation->_soundLibraryFilename = "";
+ return finished;
+}
+
+bool ScalpelEngine::scrollCredits() {
+ // Load the images for displaying credit text
+ Common::SeekableReadStream *stream = _res->load("credits.vgs", "title.lib");
+ ImageFile creditsImages(*stream);
+
+ // Demo fades slowly from the scene into credits palette
+ _screen->fadeIn(creditsImages._palette, 3);
+
+ delete stream;
+
+ // Save a copy of the screen background for use in drawing each credit frame
+ _screen->_backBuffer1.blitFrom(*_screen);
+
+ // Loop for showing the credits
+ for(int idx = 0; idx < 600 && !_events->kbHit() && !shouldQuit(); ++idx) {
+ // Copy the entire screen background before writing text
+ _screen->blitFrom(_screen->_backBuffer1);
+
+ // Write the text appropriate for the next frame
+ if (idx < 400)
+ _screen->transBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0);
+ if (idx > 200)
+ _screen->transBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0);
+
+ // Don't show credit text on the top and bottom ten rows of the screen
+ _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->w(), 10));
+ _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, _screen->h() - 10),
+ Common::Rect(0, _screen->h() - 10, _screen->w(), _screen->h()));
+
+ _events->delay(100);
+ }
+
+ return true;
+}
+
+// 3DO variant
+bool ScalpelEngine::show3DOSplash() {
+ // 3DO EA Splash screen
+ ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel);
+
+ _screen->transBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20));
+ bool finished = _events->delay(3000, true);
+
+ if (finished) {
+ _screen->clear();
+ finished = _events->delay(500, true);
+ }
+
+ if (finished) {
+ // EA logo movie
+ Scalpel3DOMoviePlay("EAlogo.stream", Common::Point(20, 0));
+ }
+
+ // Always clear screen
+ _screen->clear();
+ return finished;
+}
+
+bool ScalpelEngine::showCityCutscene3DO() {
+ _animation->_soundLibraryFilename = "TITLE.SND";
+
+ _screen->clear();
+ bool finished = _events->delay(2500, true);
+
+ // rain.aiff seems to be playing in an endless loop until
+ // sherlock logo fades away TODO
+
+ if (finished) {
+ finished = _events->delay(2500, true);
+
+ // Play intro music
+ _music->loadSong("prolog");
+
+ // Fade screen to grey
+ _screen->_backBuffer1.fill(0xCE59); // RGB565: 25, 50, 25 (grey)
+ _screen->fadeIntoScreen3DO(2);
+ }
+
+ if (finished) {
+ finished = _music->waitUntilMSec(3400, 0, 0, 3400);
+ }
+
+ if (finished) {
+ _screen->_backBuffer1.fill(0); // fill backbuffer with black to avoid issues during fade from white
+ finished = _animation->play3DO("26open1", true, 1, true, 2);
+ }
+
+ if (finished) {
+ _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade
+ _screen->_backBuffer2.blitFrom(*_screen); // save into backbuffer 2, for restoring later
+
+ // "London, England"
+ ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50));
+
+ _screen->fadeIntoScreen3DO(1);
+ finished = _events->delay(1500, true);
+
+ if (finished) {
+ // "November, 1888"
+ ImageFile3DO titleImage_November("title2b.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100));
+
+ _screen->fadeIntoScreen3DO(1);
+ finished = _music->waitUntilMSec(14700, 0, 0, 5000);
+ }
+
+ if (finished) {
+ // Restore screen
+ _screen->blitFrom(_screen->_backBuffer2);
+ }
+ }
+
+ if (finished)
+ finished = _animation->play3DO("26open2", true, 1, false, 2);
+
+ if (finished) {
+ _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade
+
+ // "Sherlock Holmes" (title)
+ ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5));
+
+ // Blend in
+ _screen->fadeIntoScreen3DO(2);
+ finished = _events->delay(500, true);
+
+ // Title should fade in, Copyright should be displayed a bit after that
+ if (finished) {
+ ImageFile3DO titleImage_Copyright("title1c.cel", kImageFile3DOType_Cel);
+
+ _screen->transBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190));
+ finished = _events->delay(3500, true);
+ }
+ }
+
+ if (finished)
+ finished = _music->waitUntilMSec(33600, 0, 0, 2000);
+
+ if (finished) {
+ // Fade to black
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(3);
+ }
+
+ if (finished) {
+ // "In the alley behind the Regency Theatre..."
+ ImageFile3DO titleImage_InTheAlley("title1d.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51));
+
+ // Fade in
+ _screen->fadeIntoScreen3DO(4);
+ finished = _music->waitUntilMSec(39900, 0, 0, 2500);
+
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+ }
+ return finished;
+}
+
+bool ScalpelEngine::showAlleyCutscene3DO() {
+ bool finished = _music->waitUntilMSec(43500, 0, 0, 1000);
+
+ if (finished)
+ finished = _animation->play3DO("27PRO1", true, 1, false, 2);
+
+ if (finished) {
+ // Fade out...
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(3);
+
+ finished = _music->waitUntilMSec(67100, 0, 0, 1000); // 66700
+ }
+
+ if (finished)
+ finished = _animation->play3DO("27PRO2", true, 1, false, 2);
+
+ if (finished)
+ finished = _music->waitUntilMSec(76000, 0, 0, 1000);
+
+ if (finished) {
+ // Show screaming victim
+ ImageFile3DO titleImage_ScreamingVictim("scream.cel", kImageFile3DOType_Cel);
+
+ _screen->clear();
+ _screen->transBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0));
+
+ // Play "scream.aiff"
+ if (_sound->_voices)
+ _sound->playSound("prologue/sounds/scream.aiff", WAIT_RETURN_IMMEDIATELY, 100);
+
+ finished = _music->waitUntilMSec(81600, 0, 0, 6000);
+ }
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(5);
+
+ finished = _music->waitUntilMSec(84400, 0, 0, 2000);
+ }
+
+ if (finished)
+ finished = _animation->play3DO("27PRO3", true, 1, false, 2);
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(5);
+ }
+
+ if (finished) {
+ // "Early the following morning on Baker Street..."
+ ImageFile3DO titleImage_EarlyTheFollowingMorning("title3.cel", kImageFile3DOType_Cel);
+ _screen->_backBuffer1.transBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51));
+
+ // Fade in
+ _screen->fadeIntoScreen3DO(4);
+ finished = _music->waitUntilMSec(96700, 0, 0, 3000);
+ }
+
+ return finished;
+}
+
+bool ScalpelEngine::showStreetCutscene3DO() {
+ bool finished = true;
+
+ if (finished) {
+ // fade out "Early the following morning..."
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+
+ // wait for music a bit
+ finished = _music->waitUntilMSec(100300, 0, 0, 1000);
+ }
+
+ if (finished)
+ finished = _animation->play3DO("14KICK", true, 1, false, 2);
+
+ // note: part of the constable is sticking to the door during the following
+ // animation, when he walks away. This is a bug of course, but it actually happened on 3DO!
+ // I'm not sure if it happens because the door is pure black (0, 0, 0) and it's because
+ // of transparency - or if the animation itself is bad. We will definitely have to adjust
+ // the animation data to fix it.
+ if (finished)
+ finished = _animation->play3DO("14NOTE", true, 1, false, 3);
+
+ if (finished) {
+ // Fade out
+ _screen->_backBuffer1.clear();
+ _screen->fadeIntoScreen3DO(4);
+ }
+
+ return finished;
+}
+
+bool ScalpelEngine::showOfficeCutscene3DO() {
+ bool finished = _music->waitUntilMSec(151000, 0, 0, 1000);
+
+ if (finished)
+ finished = _animation->play3DO("COFF1", true, 1, false, 3);
+
+ if (finished)
+ finished = _animation->play3DO("COFF2", true, 1, false, 3);
+
+ if (finished)
+ finished = _music->waitUntilMSec(182400, 0, 0, 1000);
+
+ if (finished) {
+ // Show the note
+ ImageFile3DO titleImage_CoffeeNote("note.cel", kImageFile3DOType_Cel);
+
+ _screen->clear();
+ _screen->transBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0));
+
+ if (_sound->_voices) {
+ finished = _sound->playSound("prologue/sounds/note.aiff", WAIT_KBD_OR_FINISH);
+ } else
+ finished = _events->delay(19000);
+
+ if (finished)
+ finished = _music->waitUntilMSec(218800, 0, 0, 1000);
+
+ // Fade out
+ _screen->clear();
+ }
+
+ if (finished)
+ finished = _music->waitUntilMSec(222200, 0, 0, 1000);
+
+ if (finished)
+ finished = _animation->play3DO("COFF3", true, 1, false, 3);
+
+ if (finished)
+ finished = _animation->play3DO("COFF4", true, 1, false, 3);
+
+ if (finished) {
+ finished = _music->waitUntilMSec(244500, 0, 0, 2000);
+
+ // TODO: Brighten the image, possibly by doing a partial fade
+ // to white.
+
+ _screen->_backBuffer1.blitFrom(*_screen);
+
+ for (int nr = 1; finished && nr <= 4; nr++) {
+ char filename[15];
+ sprintf(filename, "credits%d.cel", nr);
+ ImageFile3DO *creditsImage = new ImageFile3DO(filename, kImageFile3DOType_Cel);
+ ImageFrame *creditsFrame = &(*creditsImage)[0];
+ for (int i = 0; finished && i < 200 + creditsFrame->_height; i++) {
+ _screen->blitFrom(_screen->_backBuffer1);
+ _screen->transBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i));
+ if (!_events->delay(70, true))
+ finished = false;
+ }
+ delete creditsImage;
+ }
+ }
+
+ return finished;
+}
+
+void ScalpelEngine::loadInventory() {
+ ScalpelFixedText &fixedText = *(ScalpelFixedText *)_fixedText;
+ Inventory &inv = *_inventory;
+
+ Common::String fixedText_Message = fixedText.getText(kFixedText_InitInventory_Message);
+ Common::String fixedText_HolmesCard = fixedText.getText(kFixedText_InitInventory_HolmesCard);
+ Common::String fixedText_Tickets = fixedText.getText(kFixedText_InitInventory_Tickets);
+ Common::String fixedText_CuffLink = fixedText.getText(kFixedText_InitInventory_CuffLink);
+ Common::String fixedText_WireHook = fixedText.getText(kFixedText_InitInventory_WireHook);
+ Common::String fixedText_Note = fixedText.getText(kFixedText_InitInventory_Note);
+ Common::String fixedText_OpenWatch = fixedText.getText(kFixedText_InitInventory_OpenWatch);
+ Common::String fixedText_Paper = fixedText.getText(kFixedText_InitInventory_Paper);
+ Common::String fixedText_Letter = fixedText.getText(kFixedText_InitInventory_Letter);
+ Common::String fixedText_Tarot = fixedText.getText(kFixedText_InitInventory_Tarot);
+ Common::String fixedText_OrnateKey = fixedText.getText(kFixedText_InitInventory_OrnateKey);
+ Common::String fixedText_PawnTicket = fixedText.getText(kFixedText_InitInventory_PawnTicket);
+
+ // Initial inventory
+ inv._holdings = 2;
+ inv.push_back(InventoryItem(0, "Message", fixedText_Message, "_ITEM03A"));
+ inv.push_back(InventoryItem(0, "Holmes Card", fixedText_HolmesCard, "_ITEM07A"));
+
+ // Hidden items
+ inv.push_back(InventoryItem(95, "Tickets", fixedText_Tickets, "_ITEM10A"));
+ inv.push_back(InventoryItem(138, "Cuff Link", fixedText_CuffLink, "_ITEM04A"));
+ inv.push_back(InventoryItem(138, "Wire Hook", fixedText_WireHook, "_ITEM06A"));
+ inv.push_back(InventoryItem(150, "Note", fixedText_Note, "_ITEM13A"));
+ inv.push_back(InventoryItem(481, "Open Watch", fixedText_OpenWatch, "_ITEM62A"));
+ inv.push_back(InventoryItem(481, "Paper", fixedText_Paper, "_ITEM44A"));
+ inv.push_back(InventoryItem(532, "Letter", fixedText_Letter, "_ITEM68A"));
+ inv.push_back(InventoryItem(544, "Tarot", fixedText_Tarot, "_ITEM71A"));
+ inv.push_back(InventoryItem(544, "Ornate Key", fixedText_OrnateKey, "_ITEM70A"));
+ inv.push_back(InventoryItem(586, "Pawn ticket", fixedText_PawnTicket, "_ITEM16A"));
+}
+
+void ScalpelEngine::showLBV(const Common::String &filename) {
+ Common::SeekableReadStream *stream = _res->load(filename, "title.lib");
+ ImageFile images(*stream);
+ delete stream;
+
+ _screen->setPalette(images._palette);
+ _screen->_backBuffer1.blitFrom(images[0]);
+ _screen->verticalTransition();
+}
+
+void ScalpelEngine::startScene() {
+ if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) {
+ // Show the map
+ if (_music->_musicOn && _music->loadSong(100))
+ _music->startSong();
+
+ _scene->_goToScene = _map->show();
+
+ _music->freeSong();
+ _people->_savedPos = Common::Point(-1, -1);
+ _people->_savedPos._facing = -1;
+ }
+
+ // Some rooms are prologue cutscenes, rather than normal game scenes. These are:
+ // 2: Blackwood's capture
+ // 52: Rescuing Anna
+ // 53: Moorehead's death / subway train
+ // 55: Fade out and exit
+ // 70: Brumwell suicide
+ switch (_scene->_goToScene) {
+ case BLACKWOOD_CAPTURE:
+ case RESCUE_ANNA:
+ case MOOREHEAD_DEATH:
+ case BRUMWELL_SUICIDE:
+ if (_music->_musicOn && _music->loadSong(_scene->_goToScene))
+ _music->startSong();
+
+ switch (_scene->_goToScene) {
+ case BLACKWOOD_CAPTURE:
+ // Blackwood's capture
+ _res->addToCache("final2.vda", "epilogue.lib");
+ _res->addToCache("final2.vdx", "epilogue.lib");
+ _animation->play("final1", false, 1, 3, true, 4);
+ _animation->play("final2", false, 1, 0, false, 4);
+ break;
+
+ case RESCUE_ANNA:
+ // Rescuing Anna
+ _res->addToCache("finalr2.vda", "epilogue.lib");
+ _res->addToCache("finalr2.vdx", "epilogue.lib");
+ _res->addToCache("finale1.vda", "epilogue.lib");
+ _res->addToCache("finale1.vdx", "epilogue.lib");
+ _res->addToCache("finale2.vda", "epilogue.lib");
+ _res->addToCache("finale2.vdx", "epilogue.lib");
+ _res->addToCache("finale3.vda", "epilogue.lib");
+ _res->addToCache("finale3.vdx", "epilogue.lib");
+ _res->addToCache("finale4.vda", "EPILOG2.lib");
+ _res->addToCache("finale4.vdx", "EPILOG2.lib");
+
+ _animation->play("finalr1", false, 1, 3, true, 4);
+ _animation->play("finalr2", false, 1, 0, false, 4);
+
+ if (!_res->isInCache("finale2.vda")) {
+ // Finale file isn't cached
+ _res->addToCache("finale2.vda", "epilogue.lib");
+ _res->addToCache("finale2.vdx", "epilogue.lib");
+ _res->addToCache("finale3.vda", "epilogue.lib");
+ _res->addToCache("finale3.vdx", "epilogue.lib");
+ _res->addToCache("finale4.vda", "EPILOG2.lib");
+ _res->addToCache("finale4.vdx", "EPILOG2.lib");
+ }
+
+ _animation->play("finale1", false, 1, 0, false, 4);
+ _animation->play("finale2", false, 1, 0, false, 4);
+ _animation->play("finale3", false, 1, 0, false, 4);
+
+ _useEpilogue2 = true;
+ _animation->play("finale4", false, 1, 0, false, 4);
+ _useEpilogue2 = false;
+ break;
+
+ case MOOREHEAD_DEATH:
+ // Moorehead's death / subway train
+ _res->addToCache("SUBWAY2.vda", "epilogue.lib");
+ _res->addToCache("SUBWAY2.vdx", "epilogue.lib");
+ _res->addToCache("SUBWAY3.vda", "epilogue.lib");
+ _res->addToCache("SUBWAY3.vdx", "epilogue.lib");
+
+ _animation->play("SUBWAY1", false, 1, 3, true, 4);
+ _animation->play("SUBWAY2", false, 1, 0, false, 4);
+ _animation->play("SUBWAY3", false, 1, 0, false, 4);
+
+ // Set fading to direct fade temporary so the transition goes quickly.
+ _scene->_tempFadeStyle = _screen->_fadeStyle ? 257 : 256;
+ _screen->_fadeStyle = false;
+ break;
+
+ case BRUMWELL_SUICIDE:
+ // Brumwell suicide
+ _animation->play("suicid", false, 1, 3, true, 4);
+ break;
+ default:
+ break;
+ }
+
+ // Except for the Moorehead Murder scene, fade to black first
+ if (_scene->_goToScene != MOOREHEAD_DEATH) {
+ _events->wait(40);
+ _screen->fadeToBlack(3);
+ }
+
+ switch (_scene->_goToScene) {
+ case 52:
+ _scene->_goToScene = LAWYER_OFFICE; // Go to the Lawyer's Office
+ _map->_bigPos = Common::Point(0, 0); // Overland scroll position
+ _map->_overPos = Common::Point(22900 - 600, 9400 + 900); // Overland position
+ _map->_oldCharPoint = LAWYER_OFFICE;
+ break;
+
+ case 53:
+ _scene->_goToScene = STATION; // Go to St. Pancras Station
+ _map->_bigPos = Common::Point(0, 0); // Overland scroll position
+ _map->_overPos = Common::Point(32500 - 600, 3000 + 900); // Overland position
+ _map->_oldCharPoint = STATION;
+ break;
+
+ default:
+ _scene->_goToScene = BAKER_STREET; // Back to Baker st.
+ _map->_bigPos = Common::Point(0, 0); // Overland scroll position
+ _map->_overPos = Common::Point(14500 - 600, 8400 + 900); // Overland position
+ _map->_oldCharPoint = BAKER_STREET;
+ break;
+ }
+
+ // Free any song from the previous scene
+ _music->freeSong();
+ break;
+
+ case EXIT_GAME:
+ // Exit game
+ _screen->fadeToBlack(3);
+ quitGame();
+ return;
+
+ default:
+ break;
+ }
+
+ _events->setCursor(ARROW);
+
+ if (_scene->_goToScene == 99) {
+ // Darts Board minigame
+ _darts->playDarts();
+ _mapResult = _scene->_goToScene = PUB_INTERIOR;
+ }
+
+ _mapResult = _scene->_goToScene;
+}
+
+void ScalpelEngine::eraseMirror12() {
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
+
+ // If player is in range of the mirror, then restore background from the secondary back buffer
+ if (Common::Rect(70, 100, 200, 200).contains(pt)) {
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 18),
+ Common::Rect(137, 18, 184, 74));
+ }
+}
+
+void ScalpelEngine::doMirror12() {
+ People &people = *_people;
+ Person &player = people[HOLMES];
+
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
+ int frameNum = player._walkSequences[player._sequenceNumber][player._frameNumber] +
+ player._walkSequences[player._sequenceNumber][0] - 2;
+
+ switch ((*_people)[HOLMES]._sequenceNumber) {
+ case WALK_DOWN:
+ frameNum -= 7;
+ break;
+ case WALK_UP:
+ frameNum += 7;
+ break;
+ case WALK_DOWNRIGHT:
+ frameNum += 7;
+ break;
+ case WALK_UPRIGHT:
+ frameNum -= 7;
+ break;
+ case WALK_DOWNLEFT:
+ frameNum += 7;
+ break;
+ case WALK_UPLEFT:
+ frameNum -= 7;
+ break;
+ case STOP_DOWN:
+ frameNum -= 10;
+ break;
+ case STOP_UP:
+ frameNum += 11;
+ break;
+ case STOP_DOWNRIGHT:
+ frameNum -= 15;
+ break;
+ case STOP_DOWNLEFT:
+ frameNum -= 15;
+ break;
+ case STOP_UPRIGHT:
+ case STOP_UPLEFT:
+ frameNum += 15;
+ if (frameNum == 55)
+ frameNum = 54;
+ break;
+ default:
+ break;
+ }
+
+ if (Common::Rect(80, 100, 145, 138).contains(pt)) {
+ // Get the frame of Sherlock to draw
+ ImageFrame &imageFrame = (*people[HOLMES]._images)[frameNum];
+
+ // Draw the mirror image of Holmes
+ bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT
+ || people[HOLMES]._sequenceNumber == WALK_UPRIGHT || people[HOLMES]._sequenceNumber == STOP_UPRIGHT
+ || people[HOLMES]._sequenceNumber == WALK_DOWNLEFT || people[HOLMES]._sequenceNumber == STOP_DOWNLEFT;
+ _screen->_backBuffer1.transBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped);
+
+ // Redraw the mirror borders to prevent the drawn image of Holmes from appearing outside of the mirror
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(114, 18),
+ Common::Rect(114, 18, 137, 114));
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 70),
+ Common::Rect(137, 70, 142, 114));
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(142, 71),
+ Common::Rect(142, 71, 159, 114));
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(159, 72),
+ Common::Rect(159, 72, 170, 116));
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(170, 73),
+ Common::Rect(170, 73, 184, 114));
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(184, 18),
+ Common::Rect(184, 18, 212, 114));
+ }
+}
+
+void ScalpelEngine::flushMirror12() {
+ Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
+
+ // If player is in range of the mirror, then draw the entire mirror area to the screen
+ if (Common::Rect(70, 100, 200, 200).contains(pt))
+ _screen->slamArea(137, 18, 47, 56);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel.h b/engines/sherlock/scalpel/scalpel.h
new file mode 100644
index 0000000000..d2524ccbc7
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel.h
@@ -0,0 +1,133 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_H
+#define SHERLOCK_SCALPEL_H
+
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/darts.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum {
+ BUTTON_TOP = 233,
+ BUTTON_MIDDLE = 244,
+ BUTTON_BOTTOM = 248,
+ COMMAND_FOREGROUND = 15,
+ COMMAND_HIGHLIGHTED = 10,
+ COMMAND_NULL = 248,
+ INFO_FOREGROUND = 11,
+ INFO_BACKGROUND = 1,
+ INV_FOREGROUND = 14,
+ INV_BACKGROUND = 1,
+ PEN_COLOR = 250
+};
+
+class ScalpelEngine : public SherlockEngine {
+private:
+ Darts *_darts;
+ int _mapResult;
+
+ bool show3DOSplash();
+
+ /**
+ * Show the starting city cutscene which shows the game title
+ */
+ bool showCityCutscene();
+ bool showCityCutscene3DO();
+
+ /**
+ * Show the back alley where the initial murder takes place
+ */
+ bool showAlleyCutscene();
+ bool showAlleyCutscene3DO();
+
+ /**
+ * Show the Baker Street outside cutscene
+ */
+ bool showStreetCutscene();
+ bool showStreetCutscene3DO();
+
+ /**
+ * Show Holmes and Watson at the breakfast table, lestrade's note, and then the scrolling credits
+ */
+ bool showOfficeCutscene();
+ bool showOfficeCutscene3DO();
+
+ /**
+ * Show the game credits
+ */
+ bool scrollCredits();
+
+ /**
+ * Load the default inventory for the game, which includes both the initial active inventory,
+ * as well as special pending inventory items which can appear automatically in the player's
+ * inventory once given required flags are set
+ */
+ void loadInventory();
+
+ /**
+ * Transition to show an image
+ */
+ void showLBV(const Common::String &filename);
+protected:
+ /**
+ * Game initialization
+ */
+ virtual void initialize();
+
+ /**
+ * Show the opening sequence
+ */
+ virtual void showOpening();
+
+ /**
+ * Starting a scene within the game
+ */
+ virtual void startScene();
+public:
+ ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
+ virtual ~ScalpelEngine();
+
+ /**
+ * Takes care of clearing the mirror in scene 12 (mansion drawing room), in case anything drew over it
+ */
+ void eraseMirror12();
+
+ /**
+ * Takes care of drawing Holme's reflection onto the mirror in scene 12 (mansion drawing room)
+ */
+ void doMirror12();
+
+ /**
+ * This clears the mirror in scene 12 (mansion drawing room) in case anything messed draw over it
+ */
+ void flushMirror12();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_debugger.cpp b/engines/sherlock/scalpel/scalpel_debugger.cpp
new file mode 100644
index 0000000000..7f5e1efa69
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_debugger.cpp
@@ -0,0 +1,91 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_debugger.h"
+#include "sherlock/sherlock.h"
+#include "audio/mixer.h"
+#include "audio/decoders/3do.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelDebugger::ScalpelDebugger(SherlockEngine *vm) : Debugger(vm) {
+ registerCmd("3do_playmovie", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayMovie));
+ registerCmd("3do_playaudio", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayAudio));
+}
+
+bool ScalpelDebugger::cmd3DO_PlayMovie(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: 3do_playmovie <3do-movie-file>\n");
+ return true;
+ }
+
+ // play gets postboned until debugger is closed
+ Common::String filename = argv[1];
+ _3doPlayMovieFile = filename;
+
+ return cmdExit(0, 0);
+}
+
+bool ScalpelDebugger::cmd3DO_PlayAudio(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: 3do_playaudio <3do-audio-file>\n");
+ return true;
+ }
+
+ Common::File *file = new Common::File();
+ if (!file->open(argv[1])) {
+ debugPrintf("can not open specified audio file\n");
+ return true;
+ }
+
+ Audio::AudioStream *testStream;
+ Audio::SoundHandle testHandle;
+
+ // Try to load the given file as AIFF/AIFC
+ testStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
+
+ if (testStream) {
+ g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &testHandle, testStream);
+ _vm->_events->clearEvents();
+
+ while ((!_vm->shouldQuit()) && g_system->getMixer()->isSoundHandleActive(testHandle)) {
+ _vm->_events->pollEvents();
+ g_system->delayMillis(10);
+ if (_vm->_events->kbHit()) {
+ break;
+ }
+ }
+
+ debugPrintf("playing completed\n");
+ g_system->getMixer()->stopHandle(testHandle);
+ }
+
+ return true;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_debugger.h b/engines/sherlock/scalpel/scalpel_debugger.h
new file mode 100644
index 0000000000..17a84779f0
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_debugger.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_DEBUGGER_H
+#define SHERLOCK_SCALPEL_DEBUGGER_H
+
+#include "sherlock/debugger.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+class ScalpelDebugger : public Debugger {
+private:
+ /**
+ * Plays a 3DO movie
+ */
+ bool cmd3DO_PlayMovie(int argc, const char **argv);
+
+ /**
+ * Plays a 3DO audio
+ */
+ bool cmd3DO_PlayAudio(int argc, const char **argv);
+public:
+ ScalpelDebugger(SherlockEngine *vm);
+ virtual ~ScalpelDebugger() {}
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_DEBUGGER_H */
diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.cpp b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
new file mode 100644
index 0000000000..63f84d68c6
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_fixed_text.cpp
@@ -0,0 +1,377 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+static const char *const fixedTextEN[] = {
+ // SH1: Window buttons
+ "Exit",
+ "Up",
+ "Down",
+ // SH1: Inventory buttons
+ "Exit",
+ "Look",
+ "Use",
+ "Give",
+ // SH1: Journal text
+ "Watson's Journal",
+ "Page %d",
+ // SH1: Journal buttons
+ "Exit",
+ "Back 10",
+ "Up",
+ "Down",
+ "Ahead 10",
+ "Search",
+ "First Page",
+ "Last Page",
+ "Print Text",
+ // SH1: Journal search
+ "Exit",
+ "Backward",
+ "Forward",
+ "Text Not Found !",
+ // SH1: Initial Inventory
+ "A message requesting help",
+ "A number of business cards",
+ "Opera Tickets",
+ "Cuff Link",
+ "Wire Hook",
+ "Note",
+ "An open pocket watch",
+ "A piece of paper with numbers on it",
+ "A letter folded many times",
+ "Tarot Cards",
+ "An ornate key",
+ "A pawn ticket",
+ // SH2: Verbs
+ "Open",
+ "Look",
+ "Talk",
+ "Journal"
+};
+
+// sharp-s : 0xE1 / octal 341
+// small a-umlaut: 0x84 / octal 204
+// small o-umlaut: 0x94 / octal 224
+// small u-umlaut: 0x81 / octal 201
+static const char *const fixedTextDE[] = {
+ // SH1: Window buttons
+ "Zur\201ck",
+ "Hoch",
+ "Runter",
+ // SH1: Inventory buttons
+ "Zur\201ck",
+ "Schau",
+ "Benutze",
+ "Gib",
+ // SH1: Journal text
+ "Watsons Tagebuch",
+ "Seite %d",
+ // SH1: Journal buttons
+ "Zur\201ck",
+ "10 hoch",
+ "Hoch",
+ "Runter",
+ "10 runter",
+ "Suche",
+ "Erste Seite",
+ "Letzte Seite",
+ "Drucke Text",
+ // SH1: Journal search
+ "Zur\201ck",
+ "R\201ckw\204rts", // original: "Backward"
+ "Vorw\204rts", // original: "Forward"
+ "Text nicht gefunden!",
+ // SH1: Initial Inventory
+ "Ein Hilferuf von Lestrade",
+ "Holmes' Visitenkarten",
+ "Karten f\201rs Opernhaus",
+ "Manschettenkn\224pfe",
+ "Zum Haken verbogener Drahtkorb",
+ "Mitteilung am Epstein",
+ "Eine offene Taschenuhr",
+ "Ein Zettel mit Zahlen drauf",
+ "Ein mehrfach gefalteter Briefbogen",
+ "Ein Tarock-Kartenspiel", // [sic]
+ "Ein verzierter Schl\201ssel",
+ "Ein Pfandschein",
+ // SH2: Verbs
+ "\231ffne",
+ "Schau",
+ "Rede",
+ "Tagebuch"
+};
+
+// up-side down exclamation mark - 0xAD / octal 255
+// up-side down question mark - 0xA8 / octal 250
+// n with a wave on top - 0xA4 / octal 244
+static const char *const fixedTextES[] = {
+ // SH1: Window buttons
+ "Exit",
+ "Subir",
+ "Bajar",
+ // SH1: Inventory buttons
+ "Exit",
+ "Mirar",
+ "Usar",
+ "Dar",
+ // SH1: Journal text
+ "Diario de Watson",
+ "Pagina %d",
+ // SH1: Journal buttons
+ "Exit",
+ "Retroceder",
+ "Subir",
+ "baJar",
+ "Adelante",
+ "Buscar",
+ "1a pagina",
+ "Ult pagina",
+ "Imprimir",
+ // SH1: Journal search
+ "Exit",
+ "Retroceder",
+ "Avanzar",
+ "Texto no encontrado!",
+ // SH1: Initial Inventory
+ "Un mensaje solicitando ayuda",
+ "Unas cuantas tarjetas de visita",
+ "Entradas para la opera",
+ "Unos gemelos",
+ "Un gancho de alambre",
+ "Una nota",
+ "Un reloj de bolsillo abierto",
+ "Un trozo de papel con unos numeros",
+ "Un carta muy plegada",
+ "Unas cartas de Tarot",
+ "Una llave muy vistosa",
+ "Una papeleta de empe\244o",
+};
+
+// =========================================
+
+// === Sherlock Holmes 1: Serrated Scalpel ===
+static const char *const fixedTextEN_ActionOpen[] = {
+ "This cannot be opened",
+ "It is already open",
+ "It is locked",
+ "Wait for Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextDE_ActionOpen[] = {
+ "Das kann man nicht \224ffnen",
+ "Ist doch schon offen!",
+ "Leider verschlossen",
+ "Warte auf Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextES_ActionOpen[] = {
+ "No puede ser abierto",
+ "Ya esta abierto",
+ "Esta cerrado",
+ "Espera a Watson",
+ " ",
+ "."
+};
+
+static const char *const fixedTextEN_ActionClose[] = {
+ "This cannot be closed",
+ "It is already closed",
+ "The safe door is in the way"
+};
+
+static const char *const fixedTextDE_ActionClose[] = {
+ "Das kann man nicht schlie\341en",
+ "Ist doch schon zu!",
+ "Die safet\201r ist Weg"
+};
+
+static const char *const fixedTextES_ActionClose[] = {
+ "No puede ser cerrado",
+ "Ya esta cerrado",
+ "La puerta de seguridad esta entre medias"
+};
+
+static const char *const fixedTextEN_ActionMove[] = {
+ "This cannot be moved",
+ "It is bolted to the floor",
+ "It is too heavy",
+ "The other crate is in the way"
+};
+
+
+static const char *const fixedTextDE_ActionMove[] = {
+ "L\204\341t sich nicht bewegen",
+ "Festged\201belt in der Erde...",
+ "Oha, VIEL zu schwer",
+ "Der andere Kiste ist im Weg" // [sic]
+};
+
+static const char *const fixedTextES_ActionMove[] = {
+ "No puede moverse",
+ "Esta sujeto a la pared",
+ "Es demasiado pesado",
+ "El otro cajon esta en mitad"
+};
+
+static const char *const fixedTextEN_ActionPick[] = {
+ "Nothing of interest here",
+ "It is bolted down",
+ "It is too big to carry",
+ "It is too heavy",
+ "I think a girl would be more your type",
+ "Those flowers belong to Penny",
+ "She's far too young for you!",
+ "I think a girl would be more your type!",
+ "Government property for official use only"
+};
+
+static const char *const fixedTextDE_ActionPick[] = {
+ "Nichts Interessantes da",
+ "Zu gut befestigt",
+ "Ist ja wohl ein bi\341chen zu gro\341, oder ?",
+ "Oha, VIEL zu schwer",
+ "Ich denke, Du stehst mehr auf M\204dchen ?",
+ "Diese Blumen geh\224ren Penny",
+ "Sie ist doch viel zu jung f\201r Dich!",
+ "Ich denke, Du stehst mehr auf M\204dchen ?",
+ "Staatseigentum - Nur f\201r den Dienstgebrauch !"
+};
+
+static const char *const fixedTextES_ActionPick[] = {
+ "No hay nada interesante",
+ "Esta anclado al suelo",
+ "Es muy grande para llevarlo",
+ "Pesa demasiado",
+ "Creo que una chica sera mas tu tipo",
+ "Esas flores pertenecen a Penny",
+ "\255Es demasiado joven para ti!",
+ "\255Creo que una chica sera mas tu tipo!",
+ "Propiedad del gobierno para uso oficial"
+};
+
+static const char *const fixedTextEN_ActionUse[] = {
+ "You can't do that",
+ "It had no effect",
+ "You can't reach it",
+ "OK, the door looks bigger! Happy?",
+ "Doors don't smoke"
+};
+
+static const char *const fixedTextDE_ActionUse[] = {
+ "Nein, das geht wirklich nicht",
+ "Tja keinerlei Wirkung",
+ "Da kommst du nicht dran",
+ "Na gut, die T\201r sieht jetzt gr\224\341er aus. Zufrieden?",
+ "T\201ren sind Nichtraucher!"
+};
+
+static const char *const fixedTextES_ActionUse[] = {
+ "No puedes hacerlo",
+ "No tuvo ningun efecto",
+ "No puedes alcanzarlo",
+ "Bien, \255es enorme! \250Feliz?",
+ "Las puertas no fuman"
+};
+
+#define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *)
+#define FIXEDTEXT_ENTRY(_name_) _name_, FIXEDTEXT_GETCOUNT(_name_)
+
+static const FixedTextActionEntry fixedTextEN_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextEN_ActionUse) }
+};
+
+static const FixedTextActionEntry fixedTextDE_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextDE_ActionUse) }
+};
+
+static const FixedTextActionEntry fixedTextES_Actions[] = {
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionOpen) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionClose) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionMove) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionPick) },
+ { FIXEDTEXT_ENTRY(fixedTextES_ActionUse) }
+};
+
+// =========================================
+
+// TODO:
+// It seems there was a French version of Sherlock Holmes 2
+static const FixedTextLanguageEntry fixedTextLanguages[] = {
+ { Common::DE_DEU, fixedTextDE, fixedTextDE_Actions },
+ { Common::ES_ESP, fixedTextES, fixedTextES_Actions },
+ { Common::EN_ANY, fixedTextEN, fixedTextEN_Actions },
+ { Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions }
+};
+
+// =========================================
+
+// =========================================
+
+ScalpelFixedText::ScalpelFixedText(SherlockEngine *vm) : FixedText(vm) {
+ // Figure out which fixed texts to use
+ Common::Language curLanguage = _vm->getLanguage();
+
+ const FixedTextLanguageEntry *curLanguageEntry = fixedTextLanguages;
+
+ while (curLanguageEntry->language != Common::UNK_LANG) {
+ if (curLanguageEntry->language == curLanguage)
+ break; // found current language
+ curLanguageEntry++;
+ }
+ _curLanguageEntry = curLanguageEntry;
+}
+
+const char *ScalpelFixedText::getText(int fixedTextId) {
+ return _curLanguageEntry->fixedTextArray[fixedTextId];
+}
+
+const Common::String ScalpelFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) {
+ assert(actionId >= 0);
+ assert(messageIndex >= 0);
+ const FixedTextActionEntry *curActionEntry = &_curLanguageEntry->actionArray[actionId];
+
+ assert(messageIndex < curActionEntry->fixedTextArrayCount);
+ return Common::String(curActionEntry->fixedTextArray[messageIndex]);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.h b/engines/sherlock/scalpel/scalpel_fixed_text.h
new file mode 100644
index 0000000000..eae86b8f27
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_fixed_text.h
@@ -0,0 +1,108 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_FIXED_TEXT_H
+#define SHERLOCK_SCALPEL_FIXED_TEXT_H
+
+#include "sherlock/fixed_text.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum FixedTextId {
+ // Window buttons
+ kFixedText_Window_Exit = 0,
+ kFixedText_Window_Up,
+ kFixedText_Window_Down,
+ // Inventory buttons
+ kFixedText_Inventory_Exit,
+ kFixedText_Inventory_Look,
+ kFixedText_Inventory_Use,
+ kFixedText_Inventory_Give,
+ // Journal text
+ kFixedText_Journal_WatsonsJournal,
+ kFixedText_Journal_Page,
+ // Journal buttons
+ kFixedText_Journal_Exit,
+ kFixedText_Journal_Back10,
+ kFixedText_Journal_Up,
+ kFixedText_Journal_Down,
+ kFixedText_Journal_Ahead10,
+ kFixedText_Journal_Search,
+ kFixedText_Journal_FirstPage,
+ kFixedText_Journal_LastPage,
+ kFixedText_Journal_PrintText,
+ // Journal search
+ kFixedText_JournalSearch_Exit,
+ kFixedText_JournalSearch_Backward,
+ kFixedText_JournalSearch_Forward,
+ kFixedText_JournalSearch_NotFound,
+ // Initial inventory
+ kFixedText_InitInventory_Message,
+ kFixedText_InitInventory_HolmesCard,
+ kFixedText_InitInventory_Tickets,
+ kFixedText_InitInventory_CuffLink,
+ kFixedText_InitInventory_WireHook,
+ kFixedText_InitInventory_Note,
+ kFixedText_InitInventory_OpenWatch,
+ kFixedText_InitInventory_Paper,
+ kFixedText_InitInventory_Letter,
+ kFixedText_InitInventory_Tarot,
+ kFixedText_InitInventory_OrnateKey,
+ kFixedText_InitInventory_PawnTicket
+};
+
+struct FixedTextActionEntry {
+ const char *const *fixedTextArray;
+ int fixedTextArrayCount;
+};
+
+struct FixedTextLanguageEntry {
+ Common::Language language;
+ const char *const *fixedTextArray;
+ const FixedTextActionEntry *actionArray;
+};
+
+class ScalpelFixedText: public FixedText {
+private:
+ const FixedTextLanguageEntry *_curLanguageEntry;
+public:
+ ScalpelFixedText(SherlockEngine *vm);
+ virtual ~ScalpelFixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId);
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_inventory.cpp b/engines/sherlock/scalpel/scalpel_inventory.cpp
new file mode 100644
index 0000000000..e19a43238c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_inventory.cpp
@@ -0,0 +1,296 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_inventory.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelInventory::ScalpelInventory(SherlockEngine *vm) : Inventory(vm) {
+ _invShapes.resize(6);
+}
+
+ScalpelInventory::~ScalpelInventory() {
+}
+
+void ScalpelInventory::drawInventory(InvNewMode mode) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+ InvNewMode tempMode = mode;
+
+ loadInv();
+
+ if (mode == INVENTORY_DONT_DISPLAY) {
+ screen._backBuffer = &screen._backBuffer2;
+ }
+
+ // Draw the window background
+ Surface &bb = *screen._backBuffer;
+ bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2),
+ INV_BACKGROUND);
+
+ // Draw the buttons
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[0][0], CONTROLS_Y1, INVENTORY_POINTS[0][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[1][0], CONTROLS_Y1, INVENTORY_POINTS[1][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[1][2] - screen.stringWidth(fixedText_Look) / 2, fixedText_Look);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[2][0], CONTROLS_Y1, INVENTORY_POINTS[2][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[2][2] - screen.stringWidth(fixedText_Use) / 2, fixedText_Use);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[3][0], CONTROLS_Y1, INVENTORY_POINTS[3][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[3][2] - screen.stringWidth(fixedText_Give) / 2, fixedText_Give);
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[4][0], CONTROLS_Y1, INVENTORY_POINTS[4][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[4][2], "^^"); // 2 arrows pointing to the left
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[5][0], CONTROLS_Y1, INVENTORY_POINTS[5][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[5][2], "^"); // 1 arrow pointing to the left
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[6][0], CONTROLS_Y1, INVENTORY_POINTS[6][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[6][2], "_"); // 1 arrow pointing to the right
+ screen.makeButton(Common::Rect(INVENTORY_POINTS[7][0], CONTROLS_Y1, INVENTORY_POINTS[7][1],
+ CONTROLS_Y1 + 10), INVENTORY_POINTS[7][2], "__"); // 2 arrows pointing to the right
+
+ if (tempMode == INVENTORY_DONT_DISPLAY)
+ mode = LOOK_INVENTORY_MODE;
+ _invMode = (InvMode)((int)mode);
+
+ if (mode != PLAIN_INVENTORY) {
+ ui._oldKey = INVENTORY_COMMANDS[(int)mode];
+ } else {
+ ui._oldKey = -1;
+ }
+
+ invCommands(0);
+ putInv(SLAM_DONT_DISPLAY);
+
+ if (tempMode != INVENTORY_DONT_DISPLAY) {
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow(false, CONTROLS_Y1);
+ }
+
+ ui._windowOpen = true;
+ } else {
+ // Reset the screen back buffer to the first buffer now that drawing is done
+ screen._backBuffer = &screen._backBuffer1;
+ }
+
+ assert(IS_SERRATED_SCALPEL);
+ ((ScalpelUserInterface *)_vm->_ui)->_oldUse = -1;
+}
+
+void ScalpelInventory::invCommands(bool slamIt) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
+ _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
+ true, fixedText_Exit);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
+ _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND,
+ true, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
+ _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ true, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
+ _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ true, fixedText_Give);
+ screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^^"); // 2 arrows pointing to the left
+ screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^"); // 2 arrows pointing to the left
+ screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1),
+ (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "_"); // 1 arrow pointing to the right
+ screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1),
+ (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "__"); // 2 arrows pointing to the right
+ if (_invMode != INVMODE_LOOK)
+ ui.clearInfo();
+ } else {
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1),
+ _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1),
+ _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1),
+ _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1),
+ _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND,
+ false, fixedText_Give);
+ screen.gPrint(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^^"); // 2 arrows pointing to the left
+ screen.gPrint(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1),
+ _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "^"); // 1 arrow pointing to the left
+ screen.gPrint(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1),
+ (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "_"); // 1 arrow pointing to the right
+ screen.gPrint(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1),
+ (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND,
+ "__"); // 2 arrows pointing to the right
+ }
+}
+
+void ScalpelInventory::highlight(int index, byte color) {
+ Screen &screen = *_vm->_screen;
+ Surface &bb = *screen._backBuffer;
+ int slot = index - _invIndex;
+ ImageFrame &frame = (*_invShapes[slot])[0];
+
+ bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color);
+ bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2),
+ 163 + ((33 - frame._height) / 2)));
+ screen.slamArea(8 + slot * 52, 165, 44, 30);
+}
+
+void ScalpelInventory::refreshInv() {
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+
+ ui._invLookFlag = true;
+ freeInv();
+
+ ui._infoFlag = true;
+ ui.clearInfo();
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y),
+ Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ ui.examine();
+
+ if (!talk._talkToAbort) {
+ screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y));
+ loadInv();
+ }
+}
+
+void ScalpelInventory::putInv(InvSlamMode slamIt) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // If an inventory item has disappeared (due to using it or giving it),
+ // a blank space slot may have appeared. If so, adjust the inventory
+ if (_invIndex > 0 && _invIndex > (_holdings - (int)_invShapes.size())) {
+ --_invIndex;
+ freeGraphics();
+ loadGraphics();
+ }
+
+ if (slamIt != SLAM_SECONDARY_BUFFER) {
+ screen.makePanel(Common::Rect(6, 163, 54, 197));
+ screen.makePanel(Common::Rect(58, 163, 106, 197));
+ screen.makePanel(Common::Rect(110, 163, 158, 197));
+ screen.makePanel(Common::Rect(162, 163, 210, 197));
+ screen.makePanel(Common::Rect(214, 163, 262, 197));
+ screen.makePanel(Common::Rect(266, 163, 314, 197));
+ }
+
+ // Iterate through displaying up to 6 objects at a time
+ for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < (int)_invShapes.size(); ++idx) {
+ int itemNum = idx - _invIndex;
+ Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1;
+ Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194);
+
+ // Draw the background
+ if (idx == ui._selector) {
+ bb.fillRect(r, BUTTON_BACKGROUND);
+ }
+ else if (slamIt == SLAM_SECONDARY_BUFFER) {
+ bb.fillRect(r, BUTTON_MIDDLE);
+ }
+
+ // Draw the item image
+ ImageFrame &frame = (*_invShapes[itemNum])[0];
+ bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2),
+ 163 + ((33 - frame._height) / 2)));
+ }
+
+ if (slamIt == SLAM_DISPLAY)
+ screen.slamArea(6, 163, 308, 34);
+
+ if (slamIt != SLAM_SECONDARY_BUFFER)
+ ui.clearInfo();
+
+ if (slamIt == 0) {
+ invCommands(0);
+ }
+ else if (slamIt == SLAM_SECONDARY_BUFFER) {
+ screen._backBuffer = &screen._backBuffer2;
+ invCommands(0);
+ screen._backBuffer = &screen._backBuffer1;
+ }
+}
+
+void ScalpelInventory::loadInv() {
+ // Exit if the inventory names are already loaded
+ if (_names.size() > 0)
+ return;
+
+ // Load the inventory names
+ Common::SeekableReadStream *stream = _vm->_res->load("invent.txt");
+
+ int streamSize = stream->size();
+ while (stream->pos() < streamSize) {
+ Common::String name;
+ char c;
+ while ((c = stream->readByte()) != 0)
+ name += c;
+
+ _names.push_back(name);
+ }
+
+ delete stream;
+
+ loadGraphics();
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_inventory.h b/engines/sherlock/scalpel/scalpel_inventory.h
new file mode 100644
index 0000000000..afafb0b94a
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_inventory.h
@@ -0,0 +1,74 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_INVENTORY_H
+#define SHERLOCK_SCALPEL_INVENTORY_H
+
+#include "sherlock/inventory.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+class ScalpelInventory : public Inventory {
+public:
+ ScalpelInventory(SherlockEngine *vm);
+ ~ScalpelInventory();
+
+ /**
+ * Put the game into inventory mode and open the interface window.
+ */
+ void drawInventory(InvNewMode flag);
+
+ /**
+ * Prints the line of inventory commands at the top of an inventory window with
+ * the correct highlighting
+ */
+ void invCommands(bool slamIt);
+
+ /**
+ * Set the highlighting color of a given inventory item
+ */
+ void highlight(int index, byte color);
+
+ /**
+ * Support method for refreshing the display of the inventory
+ */
+ void refreshInv();
+
+ /**
+ * Display the character's inventory. The slamIt parameter specifies:
+ */
+ void putInv(InvSlamMode slamIt);
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp
new file mode 100644
index 0000000000..8e356c0f65
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_journal.cpp
@@ -0,0 +1,667 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/journal.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/tattoo/tattoo_journal.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+#define JOURNAL_BUTTONS_Y 178
+#define JOURNAL_SEARCH_LEFT 15
+#define JOURNAL_SEARCH_TOP 186
+#define JOURNAL_SEARCH_RIGHT 296
+#define JOURNAL_SEACRH_MAX_CHARS 50
+
+// Positioning of buttons in the journal view
+static const int JOURNAL_POINTS[9][3] = {
+ { 6, 68, 37 },
+ { 69, 131, 100 },
+ { 132, 192, 162 },
+ { 193, 250, 221 },
+ { 251, 313, 281 },
+ { 6, 82, 44 },
+ { 83, 159, 121 },
+ { 160, 236, 198 },
+ { 237, 313, 275 }
+};
+
+static const int SEARCH_POINTS[3][3] = {
+ { 51, 123, 86 },
+ { 124, 196, 159 },
+ { 197, 269, 232 }
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) {
+ // Initialize fields
+ _maxPage = 0;
+ _index = 0;
+ _sub = 0;
+ _up = _down = false;
+ _page = 1;
+
+ if (_vm->_interactiveFl) {
+ // Load the journal directory and location names
+ loadLocations();
+ }
+}
+
+void ScalpelJournal::record(int converseNum, int statementNum, bool replyOnly) {
+ int saveIndex = _index;
+ int saveSub = _sub;
+
+ if (IS_3DO) {
+ // there seems to be no journal in the 3DO version
+ return;
+ }
+
+ // Record the entry into the list
+ _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly));
+ _index = _journal.size() - 1;
+
+ // Load the text for the new entry to get the number of lines it will have
+ loadJournalFile(true);
+
+ // Restore old state
+ _index = saveIndex;
+ _sub = saveSub;
+
+ // If new lines were added to the ournal, update the total number of lines
+ // the journal continues
+ if (!_lines.empty()) {
+ _maxPage += _lines.size();
+ } else {
+ // No lines in entry, so remove the new entry from the journal
+ _journal.remove_at(_journal.size() - 1);
+ }
+}
+
+void ScalpelJournal::loadLocations() {
+ Resources &res = *_vm->_res;
+
+ _directory.clear();
+ _locations.clear();
+
+
+ Common::SeekableReadStream *dir = res.load("talk.lib");
+ dir->skip(4); // Skip header
+
+ // Get the numer of entries
+ _directory.resize(dir->readUint16LE());
+
+ // Read in each entry
+ char buffer[17];
+ for (uint idx = 0; idx < _directory.size(); ++idx) {
+ dir->read(buffer, 17);
+ buffer[16] = '\0';
+
+ _directory[idx] = Common::String(buffer);
+ }
+
+ delete dir;
+
+ if (IS_3DO) {
+ // 3DO: storage of locations is currently unknown TODO
+ return;
+ }
+
+ // Load in the locations stored in journal.txt
+ Common::SeekableReadStream *loc = res.load("journal.txt");
+
+ while (loc->pos() < loc->size()) {
+ Common::String line;
+ char c;
+ while ((c = loc->readByte()) != 0)
+ line += c;
+
+ _locations.push_back(line);
+ }
+
+ delete loc;
+}
+
+void ScalpelJournal::drawFrame() {
+ FixedText &fixedText = *_vm->_fixedText;
+ Resources &res = *_vm->_res;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte palette[PALETTE_SIZE];
+
+ // Load in the journal background
+ Common::SeekableReadStream *bg = res.load("journal.lbv");
+ bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT);
+ bg->read(palette, PALETTE_SIZE);
+ delete bg;
+
+ // Translate the palette for display
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx)
+ palette[idx] = VGA_COLOR_TRANS(palette[idx]);
+
+ Common::String fixedText_WatsonsJournal = fixedText.getText(kFixedText_Journal_WatsonsJournal);
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit);
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ // Set the palette and print the title
+ screen.setPalette(palette);
+ screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", fixedText_WatsonsJournal.c_str());
+ screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", fixedText_WatsonsJournal.c_str());
+
+ // Draw the buttons
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[1][2] - screen.stringWidth(fixedText_Back10) / 2, fixedText_Back10);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[2][2] - screen.stringWidth(fixedText_Up) / 2, fixedText_Up);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[3][2] - screen.stringWidth(fixedText_Down) / 2, fixedText_Down);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y,
+ JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10),
+ JOURNAL_POINTS[4][2] - screen.stringWidth(fixedText_Ahead10) / 2, fixedText_Ahead10);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[5][2] - screen.stringWidth(fixedText_Search) / 2, fixedText_Search);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[6][2] - screen.stringWidth(fixedText_FirstPage) / 2, fixedText_FirstPage);
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[7][2] - screen.stringWidth(fixedText_LastPage) / 2, fixedText_LastPage);
+
+ // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM
+ screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11,
+ JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21),
+ JOURNAL_POINTS[8][2] - screen.stringWidth(fixedText_PrintText) / 2, fixedText_PrintText);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11),
+ COMMAND_NULL, false, fixedText_PrintText);
+}
+
+void ScalpelJournal::drawInterface() {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ drawFrame();
+
+ if (_journal.empty()) {
+ _up = _down = 0;
+ } else {
+ drawJournal(0, 0);
+ }
+
+ doArrows();
+
+ // Show the entire screen
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+}
+
+void ScalpelJournal::doArrows() {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte color;
+
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Back10);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Up);
+
+ color = _down ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Down);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Ahead10);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_LastPage);
+
+ color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_Search);
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, fixedText_PrintText);
+
+ color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_FirstPage);
+}
+
+JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) {
+ if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10))
+ return BTN_EXIT;
+
+ if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1)
+ return BTN_BACK10;
+
+ if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _up)
+ return BTN_UP;
+
+ if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+ return BTN_DOWN;
+
+ if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y &&
+ pt.y < (JOURNAL_BUTTONS_Y + 10) && _down)
+ return BTN_AHEAD110;
+
+ if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+ return BTN_SEARCH;
+
+ if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && _up)
+ return BTN_FIRST_PAGE;
+
+ if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && _down)
+ return BTN_LAST_PAGE;
+
+ if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) &&
+ pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty())
+ return BTN_PRINT_TEXT;
+
+ return BTN_NONE;
+}
+
+bool ScalpelJournal::handleEvents(int key) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ bool doneFlag = false;
+
+ Common::Point pt = events.mousePos();
+ JournalButton btn = getHighlightedButton(pt);
+ byte color;
+
+ if (events._pressed || events._released) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit);
+ Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down);
+ Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10);
+ Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search);
+ Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage);
+ Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage);
+ Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText);
+
+ // Exit button
+ color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, fixedText_Exit);
+
+ // Back 10 button
+ if (btn == BTN_BACK10) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Back10);
+ } else if (_page > 1) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Back10);
+ }
+
+ // Up button
+ if (btn == BTN_UP) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up);
+ } else if (_up) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
+ }
+
+ // Down button
+ if (btn == BTN_DOWN) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down);
+ } else if (_down) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
+ }
+
+ // Ahead 10 button
+ if (btn == BTN_AHEAD110) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Ahead10);
+ } else if (_down) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Ahead10);
+ }
+
+ // Search button
+ if (btn == BTN_SEARCH) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_journal.empty()) {
+ color = COMMAND_NULL;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_Search);
+
+ // First Page button
+ if (btn == BTN_FIRST_PAGE) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_up) {
+ color = COMMAND_FOREGROUND;
+ } else {
+ color = COMMAND_NULL;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_FirstPage);
+
+ // Last Page button
+ if (btn == BTN_LAST_PAGE) {
+ color = COMMAND_HIGHLIGHTED;
+ } else if (_down) {
+ color = COMMAND_FOREGROUND;
+ } else {
+ color = COMMAND_NULL;
+ }
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_LastPage);
+
+ // Print Text button
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, fixedText_PrintText);
+ }
+
+ if (btn == BTN_EXIT && events._released) {
+ // Exit button pressed
+ doneFlag = true;
+
+ } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) {
+ // Scrolll up 10 pages
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) {
+ // Scroll up
+ drawJournal(1, LINES_PER_PAGE);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) {
+ // Scroll down
+ drawJournal(2, LINES_PER_PAGE);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) {
+ // Scroll down 10 pages
+ if ((_page + 10) > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) {
+ screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search");
+ bool notFound = false;
+
+ do {
+ int dir;
+ if ((dir = getSearchString(notFound)) != 0) {
+ int savedIndex = _index;
+ int savedSub = _sub;
+ int savedPage = _page;
+
+ if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) {
+ _index = savedIndex;
+ _sub = savedSub;
+ _page = savedPage;
+
+ drawFrame();
+ drawJournal(0, 0);
+ notFound = true;
+ } else {
+ doneFlag = true;
+ }
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ doneFlag = true;
+ }
+ } while (!doneFlag);
+ doneFlag = false;
+
+ } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) {
+ // First page
+ _index = _sub = 0;
+ _up = _down = false;
+ _page = 1;
+
+ drawFrame();
+ drawJournal(0, 0);
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) {
+ // Last page
+ if ((_page + 10) > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 1000 * LINES_PER_PAGE);
+
+ doArrows();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ events.wait(2);
+
+ return doneFlag;
+}
+
+int ScalpelJournal::getSearchString(bool printError) {
+ enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD };
+
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int xp;
+ int yp = 174;
+ bool flag = false;
+ Common::String name;
+ int done = 0;
+ byte color;
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_JournalSearch_Exit);
+ Common::String fixedText_Backward = fixedText.getText(kFixedText_JournalSearch_Backward);
+ Common::String fixedText_Forward = fixedText.getText(kFixedText_JournalSearch_Forward);
+ Common::String fixedText_NotFound = fixedText.getText(kFixedText_JournalSearch_NotFound);
+
+ // Draw search panel
+ screen.makePanel(Common::Rect(6, 171, 313, 199));
+ screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10),
+ SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10),
+ SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, fixedText_Backward);
+ screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10),
+ SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, fixedText_Forward);
+
+ screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Exit[0]);
+ screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Backward[0]);
+ screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, yp),
+ COMMAND_HIGHLIGHTED, "%c", fixedText_Forward[0]);
+
+ screen.makeField(Common::Rect(12, 185, 307, 196));
+
+ screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM);
+ screen.vLine(12, 185, 195, BUTTON_BOTTOM);
+ screen.hLine(13, 195, 306, BUTTON_TOP);
+ screen.hLine(306, 186, 195, BUTTON_TOP);
+
+ if (printError) {
+ screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(fixedText_NotFound)) / 2, 185),
+ INV_FOREGROUND, "%s", fixedText_NotFound.c_str());
+ } else if (!_find.empty()) {
+ // There's already a search term, display it already
+ screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(6, 171, 307, 28);
+
+ if (printError) {
+ // Give time for user to see the message
+ events.setButtonState();
+ for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) {
+ events.pollEvents();
+ events.setButtonState();
+ events.wait(2);
+ }
+
+ events.clearKeyboard();
+ screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE);
+
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name);
+ yp = JOURNAL_SEARCH_TOP;
+
+ do {
+ events._released = false;
+ Button found = BTN_NONE;
+
+ while (!_vm->shouldQuit() && !events.kbHit() && !events._released) {
+ found = BTN_NONE;
+ if (talk._talkToAbort)
+ return 0;
+
+ // Check if key or mouse button press has occurred
+ events.setButtonState();
+ Common::Point pt = events.mousePos();
+
+ flag = !flag;
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE);
+
+ if (events._pressed || events._released) {
+ if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_EXIT;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, 175), color, "%s", fixedText_Exit.c_str());
+
+ if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_BACKWARD;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, 175), color, "%s", fixedText_Backward.c_str());
+
+ if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) {
+ found = BTN_FORWARD;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+ screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, 175), color, "%s", fixedText_Forward.c_str());
+ }
+
+ events.wait(2);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) {
+ screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ xp -= screen.charWidth(name.lastChar());
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND);
+ name.deleteLastChar();
+
+ } else if (keyState.keycode == Common::KEYCODE_RETURN) {
+ done = 1;
+
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ done = -1;
+
+ } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT &&
+ name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) {
+ char ch = toupper(keyState.ascii);
+ screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE);
+ screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch);
+ xp += screen.charWidth(ch);
+ name += ch;
+ }
+ }
+
+ if (events._released) {
+ switch (found) {
+ case BTN_EXIT:
+ done = -1; break;
+ case BTN_BACKWARD:
+ done = 2; break;
+ case BTN_FORWARD:
+ done = 1; break;
+ default:
+ break;
+ }
+ }
+ } while (!done && !_vm->shouldQuit());
+
+ if (done != -1) {
+ _find = name;
+ } else {
+ done = 0;
+ }
+
+ // Redisplay the journal screen
+ drawFrame();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ return done;
+}
+
+void ScalpelJournal::resetPosition() {
+ _index = _sub = _up = _down = 0;
+ _page = 1;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h
new file mode 100644
index 0000000000..6bc0aa012c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_journal.h
@@ -0,0 +1,102 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_JOURNAL_H
+#define SHERLOCK_SCALPEL_JOURNAL_H
+
+#include "sherlock/journal.h"
+#include "sherlock/saveload.h"
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "common/stream.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+#define JOURNAL_MAX_WIDTH 230
+#define JOURNAL_MAX_CHARS 80
+
+enum JournalButton {
+ BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH,
+ BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT
+};
+
+class ScalpelJournal: public Journal {
+private:
+ /**
+ * Load the list of journal locations
+ */
+ void loadLocations();
+
+ /**
+ * Display the arrows that can be used to scroll up and down pages
+ */
+ void doArrows();
+
+ /**
+ * Show the search submenu and allow the player to enter a search string
+ */
+ int getSearchString(bool printError);
+
+ /**
+ * Returns the button, if any, that is under the specified position
+ */
+ JournalButton getHighlightedButton(const Common::Point &pt);
+public:
+ ScalpelJournal(SherlockEngine *vm);
+ virtual ~ScalpelJournal() {}
+
+ /**
+ * Display the journal
+ */
+ void drawInterface();
+
+ /**
+ * Handle events whilst the journal is being displayed
+ */
+ bool handleEvents(int key);
+public:
+ /**
+ * Draw the journal background, frame, and interface buttons
+ */
+ virtual void drawFrame();
+
+ /**
+ * Records statements that are said, in the order which they are said. The player
+ * can then read the journal to review them
+ */
+ virtual void record(int converseNum, int statementNum, bool replyOnly = false);
+
+ /**
+ * Reset viewing position to the start of the journal
+ */
+ virtual void resetPosition();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_map.cpp b/engines/sherlock/scalpel/scalpel_map.cpp
new file mode 100644
index 0000000000..369822ba02
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_map.cpp
@@ -0,0 +1,596 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/system.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/events.h"
+#include "sherlock/people.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+MapPaths::MapPaths() {
+ _numLocations = 0;
+}
+
+void MapPaths::load(int numLocations, Common::SeekableReadStream &s) {
+ _numLocations = numLocations;
+ _paths.resize(_numLocations * _numLocations);
+
+ for (int idx = 0; idx < (numLocations * numLocations); ++idx) {
+ Common::Array<byte> &path = _paths[idx];
+ int v;
+
+ do {
+ v = s.readByte();
+ path.push_back(v);
+ } while (v && v < 254);
+ }
+}
+
+const byte *MapPaths::getPath(int srcLocation, int destLocation) {
+ return &_paths[srcLocation * _numLocations + destLocation][0];
+}
+
+/*----------------------------------------------------------------*/
+
+ScalpelMap::ScalpelMap(SherlockEngine *vm): Map(vm), _topLine(g_system->getWidth(), 12) {
+ _mapCursors = nullptr;
+ _shapes = nullptr;
+ _iconShapes = nullptr;
+ _point = 0;
+ _placesShown = false;
+ _cursorIndex = -1;
+ _drawMap = false;
+ _overPos = Point32(130 * FIXED_INT_MULTIPLIER, 126 * FIXED_INT_MULTIPLIER);
+ _frameChangeFlag = false;
+
+ // Initialise the initial walk sequence set
+ _walkSequences.resize(MAX_HOLMES_SEQUENCE);
+ for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) {
+ _walkSequences[idx]._sequences.resize(MAX_FRAME);
+ Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0);
+ }
+
+ if (!_vm->isDemo())
+ loadData();
+}
+
+void ScalpelMap::loadPoints(int count, const int *xList, const int *yList, const int *transList) {
+ for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) {
+ _points.push_back(MapEntry(*xList, *yList, *transList));
+ }
+}
+
+void ScalpelMap::loadSequences(int count, const byte *seq) {
+ for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME)
+ Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]);
+}
+
+void ScalpelMap::loadData() {
+ // Load the list of location names
+ Common::SeekableReadStream *txtStream = _vm->_res->load("chess.txt");
+
+ int streamSize = txtStream->size();
+ while (txtStream->pos() < streamSize) {
+ Common::String line;
+ char c;
+ while ((c = txtStream->readByte()) != '\0')
+ line += c;
+
+ _locationNames.push_back(line);
+ }
+
+ delete txtStream;
+
+ // Load the path data
+ Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth");
+
+ // Get routes between different locations on the map
+ _paths.load(31, *pathStream);
+
+ // Load in the co-ordinates that the paths refer to
+ _pathPoints.resize(208);
+ for (uint idx = 0; idx < _pathPoints.size(); ++idx) {
+ _pathPoints[idx].x = pathStream->readSint16LE();
+ _pathPoints[idx].y = pathStream->readSint16LE();
+ }
+
+ delete pathStream;
+}
+
+int ScalpelMap::show() {
+ Debugger &debugger = *_vm->_debugger;
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ bool changed = false, exitFlag = false;
+ _active = true;
+
+ // Set font and custom cursor for the map
+ int oldFont = screen.fontNumber();
+ screen.setFont(0);
+
+ // Initial screen clear
+ screen._backBuffer1.clear();
+ screen.clear();
+
+ // Load the entire map
+ ImageFile *bigMap = NULL;
+ if (!IS_3DO) {
+ // PC
+ bigMap = new ImageFile("bigmap.vgs");
+ screen.setPalette(bigMap->_palette);
+ } else {
+ // 3DO
+ bigMap = new ImageFile3DO("overland.cel", kImageFile3DOType_Cel);
+ }
+
+ // Load need sprites
+ setupSprites();
+
+ if (!IS_3DO) {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ } else {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ }
+
+ _drawMap = true;
+ _charPoint = -1;
+ _point = -1;
+ people[HOLMES]._position = _lDrawnPos = _overPos;
+
+ // Show place icons
+ showPlaces();
+ saveTopLine();
+ _placesShown = true;
+
+ // Keep looping until either a location is picked, or the game is ended
+ while (!_vm->shouldQuit() && !exitFlag) {
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ if (debugger._showAllLocations == LOC_REFRESH) {
+ showPlaces();
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
+ }
+
+ // Keyboard handling
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) {
+ // Both space and enter simulate a mouse release
+ events._pressed = false;
+ events._released = true;
+ events._oldButtons = 0;
+ }
+ }
+
+ // Ignore scrolling attempts until the screen is drawn
+ if (!_drawMap) {
+ Common::Point pt = events.mousePos();
+
+ // Check for vertical map scrolling
+ if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) {
+ if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10))
+ _bigPos.y += 10;
+ else
+ _bigPos.y -= 10;
+
+ changed = true;
+ }
+
+ // Check for horizontal map scrolling
+ if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) {
+ if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10))
+ _bigPos.x += 15;
+ else
+ _bigPos.x -= 15;
+
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ // Map has scrolled, so redraw new map view
+ changed = false;
+
+ if (!IS_3DO) {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y));
+ screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y));
+ } else {
+ screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y));
+ }
+
+ showPlaces();
+ _placesShown = false;
+
+ saveTopLine();
+ _savedPos.x = -1;
+ updateMap(true);
+ } else if (!_drawMap) {
+ if (!_placesShown) {
+ showPlaces();
+ _placesShown = true;
+ }
+
+ if (_cursorIndex == 0) {
+ Common::Point pt = events.mousePos();
+ highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y));
+ }
+ updateMap(false);
+ }
+
+ if ((events._released || events._rightReleased) && _point != -1) {
+ if (people[HOLMES]._walkCount == 0) {
+ people[HOLMES]._walkDest = _points[_point] + Common::Point(4, 9);
+ _charPoint = _point;
+
+ // Start walking to selected location
+ walkTheStreets();
+
+ // Show wait cursor
+ _cursorIndex = 1;
+ events.setCursor((*_mapCursors)[_cursorIndex]._frame);
+ }
+ }
+
+ // Check if a scene has beeen selected and we've finished "moving" to it
+ if (people[HOLMES]._walkCount == 0) {
+ if (_charPoint >= 1 && _charPoint < (int)_points.size())
+ exitFlag = true;
+ }
+
+ if (_drawMap) {
+ _drawMap = false;
+
+ if (screen._fadeStyle)
+ screen.randomTransition();
+ else
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ // Wait for a frame
+ events.wait(1);
+ }
+
+ freeSprites();
+ _overPos = people[HOLMES]._position;
+
+ // Reset font
+ screen.setFont(oldFont);
+
+ // Free map graphic
+ delete bigMap;
+
+ _active = false;
+ return _charPoint;
+}
+
+void ScalpelMap::setupSprites() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ _savedPos.x = -1;
+
+ if (!IS_3DO) {
+ // PC
+ _mapCursors = new ImageFile("omouse.vgs");
+ _shapes = new ImageFile("mapicon.vgs");
+ _iconShapes = new ImageFile("overicon.vgs");
+ } else {
+ // 3DO
+ _mapCursors = new ImageFile3DO("omouse.vgs", kImageFile3DOType_RoomFormat);
+ _shapes = new ImageFile3DO("mapicon.vgs", kImageFile3DOType_RoomFormat);
+ _iconShapes = new ImageFile3DO("overicon.vgs", kImageFile3DOType_RoomFormat);
+ }
+
+ _cursorIndex = 0;
+ events.setCursor((*_mapCursors)[_cursorIndex]._frame);
+
+ _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height);
+ Person &p = people[HOLMES];
+ p._description = " ";
+ p._type = CHARACTER;
+ p._position = Common::Point(12400, 5000);
+ p._sequenceNumber = 0;
+ p._images = _shapes;
+ p._imageFrame = nullptr;
+ p._frameNumber = 0;
+ p._delta = Common::Point(0, 0);
+ p._oldSize = Common::Point(0, 0);
+ p._oldSize = Common::Point(0, 0);
+ p._misc = 0;
+ p._walkCount = 0;
+ p._allow = 0;
+ p._noShapeSize = Common::Point(0, 0);
+ p._goto = Common::Point(28000, 15000);
+ p._status = 0;
+ p._walkSequences = _walkSequences;
+ p.setImageFrame();
+ scene._bgShapes.clear();
+}
+
+void ScalpelMap::freeSprites() {
+ delete _mapCursors;
+ delete _shapes;
+ delete _iconShapes;
+ _iconSave.free();
+}
+
+void ScalpelMap::showPlaces() {
+ Debugger &debugger = *_vm->_debugger;
+ Screen &screen = *_vm->_screen;
+
+ for (uint idx = 0; idx < _points.size(); ++idx) {
+ const MapEntry &pt = _points[idx];
+
+ if (pt.x != 0 && pt.y != 0) {
+ if (debugger._showAllLocations != LOC_DISABLED)
+ _vm->setFlagsDirect(idx);
+
+ if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH
+ && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) {
+ if (_vm->readFlags(idx)) {
+ screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate],
+ Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12));
+ }
+ }
+ }
+ }
+
+ if (debugger._showAllLocations == LOC_REFRESH)
+ debugger._showAllLocations = LOC_ALL;
+}
+
+void ScalpelMap::saveTopLine() {
+ _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12));
+}
+
+void ScalpelMap::eraseTopLine() {
+ Screen &screen = *_vm->_screen;
+ screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0));
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h());
+}
+
+void ScalpelMap::showPlaceName(int idx, bool highlighted) {
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+
+ Common::String name = _locationNames[idx];
+ int width = screen.stringWidth(name);
+
+ if (!_cursorIndex) {
+ saveIcon(people[HOLMES]._imageFrame, _lDrawnPos);
+
+ bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
+ || people[HOLMES]._sequenceNumber == MAP_UPLEFT;
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped);
+ }
+
+ if (highlighted) {
+ int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2;
+ screen.gPrint(Common::Point(xp + 2, 2), BLACK, "%s", name.c_str());
+ screen.gPrint(Common::Point(xp + 1, 1), BLACK, "%s", name.c_str());
+ screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str());
+
+ screen.slamArea(xp, 0, width + 2, 15);
+ }
+}
+
+void ScalpelMap::updateMap(bool flushScreen) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Common::Point osPos = _savedPos;
+ Common::Point osSize = _savedSize;
+ Common::Point hPos;
+
+ if (_cursorIndex >= 1) {
+ if (++_cursorIndex > (1 + 8))
+ _cursorIndex = 1;
+
+ events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame);
+ }
+
+ if (!_drawMap && !flushScreen)
+ restoreIcon();
+ else
+ _savedPos.x = -1;
+
+ people[HOLMES].adjustSprite();
+
+ _lDrawnPos.x = hPos.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x;
+ _lDrawnPos.y = hPos.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y;
+
+ // Draw the person icon
+ saveIcon(people[HOLMES]._imageFrame, hPos);
+ if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT
+ || people[HOLMES]._sequenceNumber == MAP_UPLEFT)
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, true);
+ else
+ screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, false);
+
+ if (flushScreen) {
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else if (!_drawMap) {
+ if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT)
+ screen.flushImage(people[HOLMES]._imageFrame, Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y),
+ &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
+
+ if (osPos.x != -1)
+ screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y);
+ }
+}
+
+void ScalpelMap::walkTheStreets() {
+ People &people = *_vm->_people;
+ Common::Array<Common::Point> tempPath;
+
+ // Get indexes into the path lists for the start and destination scenes
+ int start = _points[_oldCharPoint]._translate;
+ int dest = _points[_charPoint]._translate;
+
+ // Get pointer to start of path
+ const byte *path = _paths.getPath(start, dest);
+
+ // Add in destination position
+ people[HOLMES]._walkTo.clear();
+ Common::Point destPos = people[HOLMES]._walkDest;
+
+ // Check for any intermediate points between the two locations
+ if (path[0] || _charPoint > 50 || _oldCharPoint > 50) {
+ people[HOLMES]._sequenceNumber = -1;
+
+ if (_charPoint == 51 || _oldCharPoint == 51) {
+ people[HOLMES].setWalking();
+ } else {
+ bool reversePath = false;
+
+ // Check for moving the path backwards or forwards
+ if (path[0] == 255) {
+ reversePath = true;
+ SWAP(start, dest);
+ path = _paths.getPath(start, dest);
+ }
+
+ do {
+ int idx = *path++;
+ tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4));
+ } while (*path != 254);
+
+ // Load up the path to use
+ people[HOLMES]._walkTo.clear();
+
+ if (reversePath) {
+ for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx)
+ people[HOLMES]._walkTo.push(tempPath[idx]);
+ } else {
+ for (int idx = 0; idx < (int)tempPath.size(); ++idx)
+ people[HOLMES]._walkTo.push(tempPath[idx]);
+ }
+
+ people[HOLMES]._walkDest = people[HOLMES]._walkTo.pop() + Common::Point(12, 6);
+ people[HOLMES].setWalking();
+ }
+ } else {
+ people[HOLMES]._walkCount = 0;
+ }
+
+ // Store the final destination icon position
+ people[HOLMES]._walkTo.push(destPos);
+}
+
+void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) {
+ Screen &screen = *_vm->_screen;
+ Common::Point size(src->_width, src->_height);
+ Common::Point pos = pt;
+
+ if (pos.x < 0) {
+ size.x += pos.x;
+ pos.x = 0;
+ }
+
+ if (pos.y < 0) {
+ size.y += pos.y;
+ pos.y = 0;
+ }
+
+ if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH)
+ size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH;
+
+ if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT)
+ size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT;
+
+ if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) {
+ // Flag as the area not needing to be saved
+ _savedPos.x = -1;
+ return;
+ }
+
+ assert(size.x <= _iconSave.w() && size.y <= _iconSave.h());
+ _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0),
+ Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y));
+ _savedPos = pos;
+ _savedSize = size;
+}
+
+void ScalpelMap::restoreIcon() {
+ Screen &screen = *_vm->_screen;
+
+ if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH
+ && _savedPos.y < SHERLOCK_SCREEN_HEIGHT)
+ screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y));
+}
+
+void ScalpelMap::highlightIcon(const Common::Point &pt) {
+ int oldPoint = _point;
+
+ // Iterate through the icon list
+ bool done = false;
+ for (int idx = 0; idx < (int)_points.size(); ++idx) {
+ const MapEntry &entry = _points[idx];
+
+ // Check whether the mouse is over a given icon
+ if (entry.x != 0 && entry.y != 0) {
+ if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) {
+ done = true;
+
+ if (_point != idx && _vm->readFlags(idx)) {
+ // Changed to a new valid (visible) location
+ eraseTopLine();
+ showPlaceName(idx, true);
+ _point = idx;
+ }
+ }
+ }
+ }
+
+ if (!done) {
+ // No icon was highlighted
+ if (_point != -1) {
+ // No longer highlighting previously highlighted icon, so erase it
+ showPlaceName(_point, false);
+ eraseTopLine();
+ }
+
+ _point = -1;
+ } else if (oldPoint != -1 && oldPoint != _point) {
+ showPlaceName(oldPoint, false);
+ eraseTopLine();
+ }
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_map.h b/engines/sherlock/scalpel/scalpel_map.h
new file mode 100644
index 0000000000..b17677725c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_map.h
@@ -0,0 +1,173 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_MAP_H
+#define SHERLOCK_SCALPEL_MAP_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "sherlock/surface.h"
+#include "sherlock/map.h"
+#include "sherlock/resources.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+
+struct MapEntry : Common::Point {
+ int _translate;
+
+ MapEntry() : Common::Point(), _translate(-1) {}
+
+ MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {}
+};
+
+class MapPaths {
+private:
+ int _numLocations;
+ Common::Array< Common::Array<byte> > _paths;
+
+public:
+ MapPaths();
+
+ /**
+ * Load the data for the paths between locations on the map
+ */
+ void load(int numLocations, Common::SeekableReadStream &s);
+
+ /**
+ * Get the path between two locations on the map
+ */
+ const byte *getPath(int srcLocation, int destLocation);
+};
+
+class ScalpelMap: public Map {
+private:
+ Common::Array<MapEntry> _points; // Map locations for each scene
+ Common::StringArray _locationNames;
+ MapPaths _paths;
+ Common::Array<Common::Point> _pathPoints;
+ Common::Point _savedPos;
+ Common::Point _savedSize;
+ Surface _topLine;
+ ImageFile *_mapCursors;
+ ImageFile *_shapes;
+ ImageFile *_iconShapes;
+ WalkSequences _walkSequences;
+ Point32 _lDrawnPos;
+ int _point;
+ bool _placesShown;
+ int _cursorIndex;
+ bool _drawMap;
+ Surface _iconSave;
+protected:
+ /**
+ * Load data needed for the map
+ */
+ void loadData();
+
+ /**
+ * Load and initialize all the sprites that are needed for the map display
+ */
+ void setupSprites();
+
+ /**
+ * Free the sprites and data used by the map
+ */
+ void freeSprites();
+
+ /**
+ * Draws an icon for every place that's currently known
+ */
+ void showPlaces();
+
+ /**
+ * Makes a copy of the top rows of the screen that are used to display location names
+ */
+ void saveTopLine();
+
+ /**
+ * Erases anything shown in the top line by restoring the previously saved original map background
+ */
+ void eraseTopLine();
+
+ /**
+ * Prints the name of the specified icon
+ */
+ void showPlaceName(int idx, bool highlighted);
+
+ /**
+ * Update all on-screen sprites to account for any scrolling of the map
+ */
+ void updateMap(bool flushScreen);
+
+ /**
+ * Handle moving icon for player from their previous location on the map to a destination location
+ */
+ void walkTheStreets();
+
+ /**
+ * Save the area under the player's icon
+ */
+ void saveIcon(ImageFrame *src, const Common::Point &pt);
+
+ /**
+ * Restore the area under the player's icon
+ */
+ void restoreIcon();
+
+ /**
+ * Handles highlighting map icons, showing their names
+ */
+ void highlightIcon(const Common::Point &pt);
+public:
+ ScalpelMap(SherlockEngine *vm);
+ virtual ~ScalpelMap() {}
+
+ const MapEntry &operator[](int idx) { return _points[idx]; }
+
+ /**
+ * Loads the list of points for locations on the map for each scene
+ */
+ void loadPoints(int count, const int *xList, const int *yList, const int *transList);
+
+ /**
+ * Load the sequence data for player icon animations
+ */
+ void loadSequences(int count, const byte *seq);
+
+ /**
+ * Show the map
+ */
+ virtual int show();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_people.cpp b/engines/sherlock/scalpel/scalpel_people.cpp
new file mode 100644
index 0000000000..53876f8f1c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_people.cpp
@@ -0,0 +1,532 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+// Walk speeds
+#define MWALK_SPEED 2
+#define XWALK_SPEED 4
+#define YWALK_SPEED 1
+
+/*----------------------------------------------------------------*/
+
+void ScalpelPerson::adjustSprite() {
+ Map &map = *_vm->_map;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ if (_type == INVALID || (_type == CHARACTER && scene._animating))
+ return;
+
+ if (!talk._talkCounter && _type == CHARACTER && _walkCount) {
+ // Handle active movement for the sprite
+ _position += _delta;
+ --_walkCount;
+
+ if (!_walkCount) {
+ // If there any points left for the character to walk to along the
+ // route to a destination, then move to the next point
+ if (!people[HOLMES]._walkTo.empty()) {
+ _walkDest = people[HOLMES]._walkTo.pop();
+ setWalking();
+ } else {
+ gotoStand();
+ }
+ }
+ }
+
+ if (_type == CHARACTER && !map._active) {
+ if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) {
+ _position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) {
+ _position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) {
+ _position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+
+ if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) {
+ _position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER;
+ gotoStand();
+ }
+ } else if (!map._active) {
+ _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT);
+ _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT);
+ }
+
+ if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag))
+ ++_frameNumber;
+
+ if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() ||
+ _walkSequences[_sequenceNumber][_frameNumber] == 0) {
+ switch (_sequenceNumber) {
+ case STOP_UP:
+ case STOP_DOWN:
+ case STOP_LEFT:
+ case STOP_RIGHT:
+ case STOP_UPRIGHT:
+ case STOP_UPLEFT:
+ case STOP_DOWNRIGHT:
+ case STOP_DOWNLEFT:
+ // We're in a stop sequence, so reset back to the last frame, so
+ // the character is shown as standing still
+ --_frameNumber;
+ break;
+
+ default:
+ // Move 1 past the first frame - we need to compensate, since we
+ // already passed the frame increment
+ _frameNumber = 1;
+ break;
+ }
+ }
+
+ // Update the _imageFrame to point to the new frame's image
+ setImageFrame();
+
+ // Check to see if character has entered an exit zone
+ if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) {
+ Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2,
+ _position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2);
+ Exit *exit = scene.checkForExit(charRect);
+
+ if (exit) {
+ scene._goToScene = exit->_scene;
+
+ if (exit->_newPosition.x != 0) {
+ people._savedPos = exit->_newPosition;
+
+ if (people._savedPos._facing > 100 && people._savedPos.x < 1)
+ people._savedPos.x = 100;
+ }
+ }
+ }
+}
+
+void ScalpelPerson::gotoStand() {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ _walkTo.clear();
+ _walkCount = 0;
+
+ switch (_sequenceNumber) {
+ case Scalpel::WALK_UP:
+ _sequenceNumber = STOP_UP;
+ break;
+ case WALK_DOWN:
+ _sequenceNumber = STOP_DOWN;
+ break;
+ case TALK_LEFT:
+ case WALK_LEFT:
+ _sequenceNumber = STOP_LEFT;
+ break;
+ case TALK_RIGHT:
+ case WALK_RIGHT:
+ _sequenceNumber = STOP_RIGHT;
+ break;
+ case WALK_UPRIGHT:
+ _sequenceNumber = STOP_UPRIGHT;
+ break;
+ case WALK_UPLEFT:
+ _sequenceNumber = STOP_UPLEFT;
+ break;
+ case WALK_DOWNRIGHT:
+ _sequenceNumber = STOP_DOWNRIGHT;
+ break;
+ case WALK_DOWNLEFT:
+ _sequenceNumber = STOP_DOWNLEFT;
+ break;
+ default:
+ break;
+ }
+
+ // Only restart frame at 0 if the sequence number has changed
+ if (_oldWalkSequence != -1 || _sequenceNumber == Scalpel::STOP_UP)
+ _frameNumber = 0;
+
+ if (map._active) {
+ _sequenceNumber = 0;
+ people[HOLMES]._position.x = (map[map._charPoint].x - 6) * FIXED_INT_MULTIPLIER;
+ people[HOLMES]._position.y = (map[map._charPoint].y + 10) * FIXED_INT_MULTIPLIER;
+ }
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+}
+
+void ScalpelPerson::setWalking() {
+ Map &map = *_vm->_map;
+ Scene &scene = *_vm->_scene;
+ int oldDirection, oldFrame;
+ Common::Point speed, delta;
+
+ // Flag that player has now walked in the scene
+ scene._walkedInScene = true;
+
+ // Stop any previous walking, since a new dest is being set
+ _walkCount = 0;
+ oldDirection = _sequenceNumber;
+ oldFrame = _frameNumber;
+
+ // Set speed to use horizontal and vertical movement
+ if (map._active) {
+ speed = Common::Point(MWALK_SPEED, MWALK_SPEED);
+ } else {
+ speed = Common::Point(XWALK_SPEED, YWALK_SPEED);
+ }
+
+ // If the player is already close to the given destination that no
+ // walking is needed, move to the next straight line segment in the
+ // overall walking route, if there is one
+ for (;;) {
+ // Since we want the player to be centered on the destination they
+ // clicked, but characters draw positions start at their left, move
+ // the destination half the character width to draw him centered
+ int temp;
+ if (_walkDest.x >= (temp = _imageFrame->_frame.w / 2))
+ _walkDest.x -= temp;
+
+ delta = Common::Point(
+ ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
+ ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
+ );
+
+ // If we're ready to move a sufficient distance, that's it. Otherwise,
+ // move onto the next portion of the walk path, if there is one
+ if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
+ break;
+
+ // Pop next walk segment off the walk route stack
+ _walkDest = _walkTo.pop();
+ }
+
+ // If a sufficient move is being done, then start the move
+ if (delta.x > 3 || delta.y) {
+ // See whether the major movement is horizontal or vertical
+ if (delta.x >= delta.y) {
+ // Set the initial frame sequence for the left and right, as well
+ // as setting the delta x depending on direction
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT);
+ _delta.x = speed.x * -FIXED_INT_MULTIPLIER;
+ } else {
+ _sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT);
+ _delta.x = speed.x * FIXED_INT_MULTIPLIER;
+ }
+
+ // See if the x delta is too small to be divided by the speed, since
+ // this would cause a divide by zero error
+ if (delta.x >= speed.x) {
+ // Det the delta y
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x / speed.x);
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
+ _delta.y = -_delta.y;
+
+ // Set how many times we should add the delta to the player's position
+ _walkCount = delta.x / speed.x;
+ } else {
+ // The delta x was less than the speed (ie. we're really close to
+ // the destination). So set delta to 0 so the player won't move
+ _delta = Point32(0, 0);
+ _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);
+
+ _walkCount = 1;
+ }
+
+ // See if the sequence needs to be changed for diagonal walking
+ if (_delta.y > 150) {
+ if (!map._active) {
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_DOWNLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_DOWNRIGHT;
+ break;
+ }
+ }
+ } else if (_delta.y < -150) {
+ if (!map._active) {
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_UPLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_UPRIGHT;
+ break;
+ }
+ }
+ }
+ } else {
+ // Major movement is vertical, so set the sequence for up and down,
+ // and set the delta Y depending on the direction
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_UP;
+ _delta.y = speed.y * -FIXED_INT_MULTIPLIER;
+ } else {
+ _sequenceNumber = WALK_DOWN;
+ _delta.y = speed.y * FIXED_INT_MULTIPLIER;
+ }
+
+ // If we're on the overhead map, set the sequence so we keep moving
+ // in the same direction
+ if (map._active)
+ _sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection;
+
+ // Set the delta x
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y);
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER))
+ _delta.x = -_delta.x;
+
+ _walkCount = delta.y / speed.y;
+ }
+ }
+
+ // See if the new walk sequence is the same as the old. If it's a new one,
+ // we need to reset the frame number to zero so it's animation starts at
+ // it's beginning. Otherwise, if it's the same sequence, we can leave it
+ // as is, so it keeps the animation going at wherever it was up to
+ if (_sequenceNumber != _oldWalkSequence)
+ _frameNumber = 0;
+ _oldWalkSequence = _sequenceNumber;
+
+ if (!_walkCount)
+ gotoStand();
+
+ // If the sequence is the same as when we started, then Holmes was
+ // standing still and we're trying to re-stand him, so reset Holmes'
+ // rame to the old frame number from before it was reset to 0
+ if (_sequenceNumber == oldDirection)
+ _frameNumber = oldFrame;
+}
+
+void ScalpelPerson::walkToCoords(const Point32 &destPos, int destDir) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER + 10, destPos.y / FIXED_INT_MULTIPLIER);
+ people._allowWalkAbort = true;
+ goAllTheWay();
+
+ // Keep calling doBgAnim until the walk is done
+ do {
+ events.pollEventsAndWait();
+ scene.doBgAnim();
+ } while (!_vm->shouldQuit() && _walkCount);
+
+ if (!talk._talkToAbort) {
+ // Put character exactly on destination position, and set direction
+ _position = destPos;
+ _sequenceNumber = destDir;
+ gotoStand();
+
+ // Draw Holmes facing the new direction
+ scene.doBgAnim();
+
+ if (!talk._talkToAbort)
+ events.setCursor(oldCursor);
+ }
+}
+
+Common::Point ScalpelPerson::getSourcePoint() const {
+ return Common::Point(_position.x / FIXED_INT_MULTIPLIER + frameWidth() / 2,
+ _position.y / FIXED_INT_MULTIPLIER);
+}
+
+/*----------------------------------------------------------------*/
+
+ScalpelPeople::ScalpelPeople(SherlockEngine *vm) : People(vm) {
+ _data.push_back(new ScalpelPerson());
+}
+
+void ScalpelPeople::setTalking(int speaker) {
+ Resources &res = *_vm->_res;
+
+ // If no speaker is specified, then we can exit immediately
+ if (speaker == -1)
+ return;
+
+ if (_portraitsOn) {
+ delete _talkPics;
+ Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait);
+ _talkPics = new ImageFile(filename);
+
+ // Load portrait sequences
+ Common::SeekableReadStream *stream = res.load("sequence.txt");
+ stream->seek(speaker * MAX_FRAME);
+
+ int idx = 0;
+ do {
+ _portrait._sequences[idx] = stream->readByte();
+ ++idx;
+ } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]);
+
+ delete stream;
+
+ _portrait._maxFrames = idx;
+ _portrait._frameNumber = 0;
+ _portrait._sequenceNumber = 0;
+ _portrait._images = _talkPics;
+ _portrait._imageFrame = &(*_talkPics)[0];
+ _portrait._position = Common::Point(_portraitSide, 10);
+ _portrait._delta = Common::Point(0, 0);
+ _portrait._oldPosition = Common::Point(0, 0);
+ _portrait._goto = Common::Point(0, 0);
+ _portrait._flags = 5;
+ _portrait._status = 0;
+ _portrait._misc = 0;
+ _portrait._allow = 0;
+ _portrait._type = ACTIVE_BG_SHAPE;
+ _portrait._name = " ";
+ _portrait._description = " ";
+ _portrait._examine = " ";
+ _portrait._walkCount = 0;
+
+ if (_holmesFlip || _speakerFlip) {
+ _portrait._flags |= 2;
+
+ _holmesFlip = false;
+ _speakerFlip = false;
+ }
+
+ if (_portraitSide == 20)
+ _portraitSide = 220;
+ else
+ _portraitSide = 20;
+
+ _portraitLoaded = true;
+ }
+}
+
+void ScalpelPeople::synchronize(Serializer &s) {
+ s.syncAsByte(_holmesOn);
+ s.syncAsSint32LE(_data[HOLMES]->_position.x);
+ s.syncAsSint32LE(_data[HOLMES]->_position.y);
+ s.syncAsSint16LE(_data[HOLMES]->_sequenceNumber);
+ s.syncAsSint16LE(_holmesQuotient);
+
+ if (s.isLoading()) {
+ _savedPos = _data[HOLMES]->_position;
+ _savedPos._facing = _data[HOLMES]->_sequenceNumber;
+ }
+}
+
+void ScalpelPeople::setTalkSequence(int speaker, int sequenceNum) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ if (speaker) {
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1) {
+ Object &obj = scene._bgShapes[objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to copy too many talk frames");
+ }
+ else {
+ for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
+ obj._sequences[idx] = people._characters[speaker]._talkSequences[idx];
+ if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1])
+ return;
+
+ obj._frameNumber = 0;
+ obj._sequenceNumber = 0;
+ }
+ }
+ }
+ }
+}
+
+bool ScalpelPeople::loadWalk() {
+ bool result = false;
+
+ if (_data[HOLMES]->_walkLoaded) {
+ return false;
+ } else {
+ if (!IS_3DO) {
+ _data[HOLMES]->_images = new ImageFile("walk.vgs");
+ } else {
+ // Load walk.anim on 3DO, which is a cel animation file
+ _data[HOLMES]->_images = new ImageFile3DO("walk.anim", kImageFile3DOType_CelAnimation);
+ }
+ _data[HOLMES]->setImageFrame();
+ _data[HOLMES]->_walkLoaded = true;
+
+ result = true;
+ }
+
+ _forceWalkReload = false;
+ return result;
+}
+
+const Common::Point ScalpelPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
+ Scene &scene = *_vm->_scene;
+ Common::Point walkDest = destPos;
+
+ // The destination isn't in a zone
+ if (walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1))
+ walkDest.x = SHERLOCK_SCREEN_WIDTH - 2;
+
+ // Trace a line between the centroid of the found closest zone to
+ // the destination, to find the point at which the zone will be left
+ const Common::Rect &destRect = scene._zones[zoneId];
+ const Common::Point destCenter((destRect.left + destRect.right) / 2,
+ (destRect.top + destRect.bottom) / 2);
+ const Common::Point delta = walkDest - destCenter;
+ Point32 pt(destCenter.x * FIXED_INT_MULTIPLIER, destCenter.y * FIXED_INT_MULTIPLIER);
+
+ // Move along the line until the zone is left
+ do {
+ pt += delta;
+ } while (destRect.contains(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER));
+
+ // Set the new walk destination to the last point that was in the
+ // zone just before it was left
+ return Common::Point((pt.x - delta.x * 2) / FIXED_INT_MULTIPLIER,
+ (pt.y - delta.y * 2) / FIXED_INT_MULTIPLIER);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_people.h b/engines/sherlock/scalpel/scalpel_people.h
new file mode 100644
index 0000000000..b53da2e6d8
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_people.h
@@ -0,0 +1,114 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_PEOPLE_H
+#define SHERLOCK_SCALPEL_PEOPLE_H
+
+#include "common/scummsys.h"
+#include "sherlock/people.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+// Animation sequence identifiers for characters
+enum ScalpelSequences {
+ WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4,
+ STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8,
+ WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11,
+ STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14,
+ STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4
+};
+
+class ScalpelPerson : public Person {
+public:
+ ScalpelPerson() : Person() {}
+ virtual ~ScalpelPerson() {}
+
+ /**
+ * This adjusts the sprites position, as well as it's animation sequence:
+ */
+ virtual void adjustSprite();
+
+ /**
+ * Bring a moving character to a standing position
+ */
+ virtual void gotoStand();
+
+ /**
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
+ */
+ virtual void setWalking();
+
+ /**
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir);
+
+ /**
+ * Get the source position for a character potentially affected by scaling
+ */
+ virtual Common::Point getSourcePoint() const;
+};
+
+class ScalpelPeople : public People {
+public:
+ ScalpelPeople(SherlockEngine *vm);
+ virtual ~ScalpelPeople() {}
+
+ ScalpelPerson &operator[](PeopleId id) { return *(ScalpelPerson *)_data[id]; }
+ ScalpelPerson &operator[](int idx) { return *(ScalpelPerson *)_data[idx]; }
+
+ /**
+ * Setup the data for an animating speaker portrait at the top of the screen
+ */
+ void setTalking(int speaker);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Change the sequence of the scene background object associated with the specified speaker.
+ */
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1);
+
+ /**
+ * Restrict passed point to zone using Sherlock's positioning rules
+ */
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos);
+
+ /**
+ * Load the walking images for Sherlock
+ */
+ virtual bool loadWalk();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_saveload.cpp b/engines/sherlock/scalpel/scalpel_saveload.cpp
new file mode 100644
index 0000000000..01ba149813
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_saveload.cpp
@@ -0,0 +1,261 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+const int ENV_POINTS[6][3] = {
+ { 41, 80, 61 }, // Exit
+ { 81, 120, 101 }, // Load
+ { 121, 160, 141 }, // Save
+ { 161, 200, 181 }, // Up
+ { 201, 240, 221 }, // Down
+ { 241, 280, 261 } // Quit
+};
+
+/*----------------------------------------------------------------*/
+
+ScalpelSaveManager::ScalpelSaveManager(SherlockEngine *vm, const Common::String &target) : SaveManager(vm, target) {
+}
+
+void ScalpelSaveManager::drawInterface() {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // Create a list of savegame slots
+ createSavegameList();
+
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+
+ screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10),
+ ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit");
+ screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10),
+ ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load");
+ screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10),
+ ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save");
+ screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10),
+ ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up");
+ screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10),
+ ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down");
+ screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10),
+ ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit");
+
+ if (!_savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up");
+
+ if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down");
+
+ for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) {
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%s", _savegames[idx].c_str());
+ }
+
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow();
+ }
+
+ _envMode = SAVEMODE_NONE;
+}
+
+int ScalpelSaveManager::getHighlightedButton() const {
+ Common::Point pt = _vm->_events->mousePos();
+
+ for (int idx = 0; idx < 6; ++idx) {
+ if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y
+ && pt.y < (CONTROLS_Y + 10))
+ return idx;
+ }
+
+ return -1;
+}
+
+void ScalpelSaveManager::highlightButtons(int btnIndex) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+
+ screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit");
+
+ if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2)))
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load");
+ else
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load");
+
+ if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1)))
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save");
+ else
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save");
+
+ if (btnIndex == 3 && _savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up");
+ else
+ if (_savegameIndex)
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up");
+
+ if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5))
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down");
+ else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5))
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down");
+
+ color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit");
+}
+
+bool ScalpelSaveManager::checkGameOnScreen(int slot) {
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ // Check if it's already on-screen
+ if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) {
+ _savegameIndex = slot;
+
+ screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+
+ for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) {
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10),
+ INV_FOREGROUND, "%s", _savegames[idx].c_str());
+ }
+
+ screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT));
+
+ byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up");
+
+ color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down");
+
+ return true;
+ }
+
+ return false;
+}
+
+bool ScalpelSaveManager::promptForDescription(int slot) {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int xp, yp;
+ bool flag = false;
+
+ screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit");
+ screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load");
+ screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save");
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up");
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down");
+ screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit");
+
+ Common::String saveName = _savegames[slot];
+ if (isSlotEmpty(slot)) {
+ // It's an empty slot, so start off with an empty save name
+ saveName = "";
+
+ yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
+ screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND);
+ }
+
+ screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1);
+ screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str());
+ xp = 24 + screen.stringWidth(saveName);
+ yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10;
+
+ int done = 0;
+ do {
+ while (!_vm->shouldQuit() && !events.kbHit()) {
+ scene.doBgAnim();
+
+ if (talk._talkToAbort)
+ return false;
+
+ // Allow event processing
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ flag = !flag;
+ if (flag)
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ else
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ }
+ if (_vm->shouldQuit())
+ return false;
+
+ // Get the next keypress
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) {
+ // Delete character of save name
+ screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1,
+ xp + 8, yp + 9), INV_BACKGROUND);
+ xp -= screen.charWidth(saveName.lastChar());
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ saveName.deleteLastChar();
+
+ } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) {
+ done = 1;
+
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ done = -1;
+
+ } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50
+ && (xp + screen.charWidth(keyState.ascii)) < 308) {
+ char c = (char)keyState.ascii;
+
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND);
+ screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c);
+ xp += screen.charWidth(c);
+ screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND);
+ saveName += c;
+ }
+ } while (!done);
+
+ if (done == 1) {
+ // Enter key perssed
+ _savegames[slot] = saveName;
+ } else {
+ done = 0;
+ _envMode = SAVEMODE_NONE;
+ highlightButtons(-1);
+ }
+
+ return done == 1;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_saveload.h b/engines/sherlock/scalpel/scalpel_saveload.h
new file mode 100644
index 0000000000..db4fa1a2ab
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_saveload.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_SAVELOAD_H
+#define SHERLOCK_SCALPEL_SAVELOAD_H
+
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+extern const int ENV_POINTS[6][3];
+
+class ScalpelSaveManager: public SaveManager {
+public:
+ ScalpelSaveManager(SherlockEngine *vm, const Common::String &target);
+ virtual ~ScalpelSaveManager() {}
+
+ /**
+ * Shows the in-game dialog interface for loading and saving games
+ */
+ void drawInterface();
+
+ /**
+ * Return the index of the button the mouse is over, if any
+ */
+ int getHighlightedButton() const;
+
+ /**
+ * Handle highlighting buttons
+ */
+ void highlightButtons(int btnIndex);
+
+ /**
+ * Make sure that the selected savegame is on-screen
+ */
+ bool checkGameOnScreen(int slot);
+
+ /**
+ * Prompts the user to enter a description in a given slot
+ */
+ bool promptForDescription(int slot);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp
new file mode 100644
index 0000000000..4e6e9e4c7c
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_scene.cpp
@@ -0,0 +1,726 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/events.h"
+#include "sherlock/people.h"
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+bool ScalpelScene::loadScene(const Common::String &filename) {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ bool result = Scene::loadScene(filename);
+
+ if (!_vm->isDemo()) {
+ // Reset the previous map location and position on overhead map
+ map._oldCharPoint = _currentScene;
+
+ map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER;
+
+ }
+
+ return result;
+}
+
+void ScalpelScene::drawAllShapes() {
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+
+ // Restrict drawing window
+ screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
+
+ // Draw all active shapes which are behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame,
+ _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all active shapes which are normal and behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are normal and behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw any active characters
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Person &p = people[idx];
+ if (p._type == CHARACTER && p._walkLoaded) {
+ bool flipped = IS_SERRATED_SCALPEL && (
+ p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT ||
+ p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT ||
+ p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT);
+
+ screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER,
+ p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped);
+ }
+ }
+
+ // Draw all static and active shapes that are NORMAL and are in front of the player
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _bgShapes[idx]._misc == NORMAL_FORWARD)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
+ _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active canimations that are NORMAL and are in front of the player
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _canimShapes[idx]._misc == NORMAL_FORWARD)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active shapes that are FORWARD
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ _bgShapes[idx]._oldPosition = _bgShapes[idx]._position;
+ _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(),
+ _bgShapes[idx].frameHeight());
+
+ if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _bgShapes[idx]._misc == FORWARD)
+ screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
+ _bgShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active canimations that are forward
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) &&
+ _canimShapes[idx]._misc == FORWARD)
+ screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position,
+ _canimShapes[idx]._flags & OBJ_FLIPPED);
+ }
+
+ screen.resetDisplayBounds();
+}
+
+void ScalpelScene::checkBgShapes() {
+ People &people = *_vm->_people;
+ Person &holmes = people[HOLMES];
+ Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
+
+ // Call the base scene method to handle bg shapes
+ Scene::checkBgShapes();
+
+ // Iterate through the canim list
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &obj = _canimShapes[idx];
+ if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) {
+ if ((obj._flags & 5) == 1) {
+ obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ?
+ NORMAL_FORWARD : NORMAL_BEHIND;
+ } else if (!(obj._flags & 1)) {
+ obj._misc = BEHIND;
+ } else if (obj._flags & 4) {
+ obj._misc = FORWARD;
+ }
+ }
+ }
+}
+
+void ScalpelScene::doBgAnimCheckCursor() {
+ Inventory &inv = *_vm->_inventory;
+ Events &events = *_vm->_events;
+ Sound &sound = *_vm->_sound;
+ UserInterface &ui = *_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ events.animateCursorIfNeeded();
+
+ if (ui._menuMode == LOOK_MODE) {
+ if (mousePos.y > CONTROLS_Y1)
+ events.setCursor(ARROW);
+ else if (mousePos.y < CONTROLS_Y)
+ events.setCursor(MAGNIFY);
+ }
+
+ // Check for setting magnifying glass cursor
+ if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) {
+ if (inv._invMode == INVMODE_LOOK) {
+ // Only show Magnifying glass cursor if it's not on the inventory command line
+ if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13))
+ events.setCursor(MAGNIFY);
+ else
+ events.setCursor(ARROW);
+ } else {
+ events.setCursor(ARROW);
+ }
+ }
+
+ if (sound._diskSoundPlaying && !*sound._soundIsOn) {
+ // Loaded sound just finished playing
+ sound.freeDigiSound();
+ }
+}
+
+void ScalpelScene::doBgAnim() {
+ ScalpelEngine &vm = *((ScalpelEngine *)_vm);
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+
+ doBgAnimCheckCursor();
+
+ screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
+ talk._talkToAbort = false;
+
+ if (_restoreFlag) {
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER)
+ people[idx].checkSprite();
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE)
+ _bgShapes[idx].checkObject();
+ }
+
+ if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
+ people._portrait.checkObject();
+
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type != INVALID && _canimShapes[idx]._type != REMOVE)
+ _canimShapes[idx].checkObject();
+ }
+
+ if (_currentScene == 12)
+ vm.eraseMirror12();
+
+ // Restore the back buffer from the back buffer 2 in the changed area
+ Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
+ people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
+ people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y);
+ Common::Point pt(bounds.left, bounds.top);
+
+ if (people[HOLMES]._type == CHARACTER)
+ screen.restoreBackground(bounds);
+ else if (people[HOLMES]._type == REMOVE)
+ screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds);
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
+ screen.restoreBackground(o.getOldBounds());
+ }
+
+ if (people._portraitLoaded)
+ screen.restoreBackground(Common::Rect(
+ people._portrait._oldPosition.x, people._portrait._oldPosition.y,
+ people._portrait._oldPosition.x + people._portrait._oldSize.x,
+ people._portrait._oldPosition.y + people._portrait._oldSize.y
+ ));
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) {
+ // Restore screen area
+ screen._backBuffer->blitFrom(screen._backBuffer2, o._position,
+ Common::Rect(o._position.x, o._position.y,
+ o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y));
+
+ o._oldPosition = o._position;
+ o._oldSize = o._noShapeSize;
+ }
+ }
+
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &o = _canimShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
+ screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y,
+ o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y));
+ }
+ }
+
+ //
+ // Update the background objects and canimations
+ //
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE)
+ o.adjustObject();
+ }
+
+ if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
+ people._portrait.adjustObject();
+
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ if (_canimShapes[idx]._type != INVALID)
+ _canimShapes[idx].adjustObject();
+ }
+
+ if (people[HOLMES]._type == CHARACTER && people._holmesOn)
+ people[HOLMES].adjustSprite();
+
+ // Flag the bg shapes which need to be redrawn
+ checkBgShapes();
+
+ if (_currentScene == 12)
+ vm.doMirror12();
+
+ // Draw all active shapes which are behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND)
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &o = _canimShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) {
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+ }
+
+ // Draw all active shapes which are HAPPEN and behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND)
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all canimations which are NORMAL and behind the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &o = _canimShapes[idx];
+ if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) {
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+ }
+
+ // Draw the person if not animating
+ if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded) {
+ // If Holmes is too far to the right, move him back so he's on-screen
+ int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w;
+ int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight);
+
+ bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT ||
+ people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT ||
+ people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT;
+ screen._backBuffer->transBlitFrom(*people[HOLMES]._imageFrame,
+ Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped);
+ }
+
+ // Draw all static and active shapes are NORMAL and are in front of the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD)
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+
+ // Draw all static and active canimations that are NORMAL and are in front of the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &o = _canimShapes[idx];
+ if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) {
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+ }
+
+ // Draw all static and active shapes that are in front of the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD)
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+
+ // Draw any active portrait
+ if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
+ screen._backBuffer->transBlitFrom(*people._portrait._imageFrame,
+ people._portrait._position, people._portrait._flags & OBJ_FLIPPED);
+
+ // Draw all static and active canimations that are in front of the person
+ for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
+ Object &o = _canimShapes[idx];
+ if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) {
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+ }
+
+ // Draw all NO_SHAPE shapes which have flag bit 0 clear
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0)
+ screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
+ }
+
+ // Bring the newly built picture to the screen
+ if (_animating == 2) {
+ _animating = 0;
+ screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
+ } else {
+ if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) {
+ if (people[HOLMES]._type == REMOVE) {
+ screen.slamRect(Common::Rect(
+ people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
+ people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
+ people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y
+ ));
+ people[HOLMES]._type = INVALID;
+ } else {
+ screen.flushImage(people[HOLMES]._imageFrame,
+ Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()),
+ &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y,
+ &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
+ }
+ }
+
+ if (_currentScene == 12)
+ vm.flushMirror12();
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) {
+ screen.flushImage(o._imageFrame, o._position,
+ &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
+ }
+ }
+
+ if (people._portraitLoaded) {
+ if (people._portrait._type == REMOVE)
+ screen.slamRect(Common::Rect(
+ people._portrait._position.x, people._portrait._position.y,
+ people._portrait._position.x + people._portrait._delta.x,
+ people._portrait._position.y + people._portrait._delta.y
+ ));
+ else
+ screen.flushImage(people._portrait._imageFrame, people._portrait._position,
+ &people._portrait._oldPosition.x, &people._portrait._oldPosition.y,
+ &people._portrait._oldSize.x, &people._portrait._oldSize.y);
+
+ if (people._portrait._type == REMOVE)
+ people._portrait._type = INVALID;
+ }
+
+ if (_goToScene == -1) {
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) {
+ screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y);
+ screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y);
+ } else if (o._type == HIDE_SHAPE) {
+ // Hiding shape, so flush it out and mark it as hidden
+ screen.flushImage(o._imageFrame, o._position,
+ &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
+ o._type = HIDDEN;
+ }
+ }
+ }
+
+ for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) {
+ Object &o = _canimShapes[idx];
+
+ if (o._type == INVALID) {
+ // Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it
+ _canimShapes.remove_at(idx);
+ } else if (o._type == REMOVE) {
+ if (_goToScene == -1)
+ screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y);
+
+ // Shape for an animation is no longer needed, so remove it completely
+ _canimShapes.remove_at(idx);
+ } else if (o._type == ACTIVE_BG_SHAPE) {
+ screen.flushImage(o._imageFrame, o._position,
+ &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
+ }
+ }
+ }
+
+ _restoreFlag = true;
+ _doBgAnimDone = true;
+
+ events.wait(3);
+ screen.resetDisplayBounds();
+
+ // 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 && talk._scriptMoreFlag == 3) {
+ // Reset the flags and call to talk
+ people._clearingThePortrait = false;
+ talk._scriptMoreFlag = 0;
+ talk.talkTo(talk._scriptName);
+ }
+}
+
+int ScalpelScene::startCAnim(int cAnimNum, int playRate) {
+ Events &events = *_vm->_events;
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ Resources &res = *_vm->_res;
+ Talk &talk = *_vm->_talk;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+ Point32 tpPos, walkPos;
+ int tpDir, walkDir;
+ int tFrames = 0;
+ int gotoCode = -1;
+
+ // Validation
+ if (cAnimNum >= (int)_cAnim.size())
+ // number out of bounds
+ return -1;
+ if (_canimShapes.size() >= 3 || playRate == 0)
+ // Too many active animations, or invalid play rate
+ return 0;
+
+ CAnim &cAnim = _cAnim[cAnimNum];
+ if (playRate < 0) {
+ // Reverse direction
+ walkPos = cAnim._teleport[0];
+ walkDir = cAnim._teleport[0]._facing;
+ tpPos = cAnim._goto[0];
+ tpDir = cAnim._goto[0]._facing;
+ } else {
+ // Forward direction
+ walkPos = cAnim._goto[0];
+ walkDir = cAnim._goto[0]._facing;
+ tpPos = cAnim._teleport[0];
+ tpDir = cAnim._teleport[0]._facing;
+ }
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ if (walkPos.x != -1) {
+ // Holmes must walk to the walk point before the cAnimation is started
+ if (people[HOLMES]._position != walkPos)
+ people[HOLMES].walkToCoords(walkPos, walkDir);
+ }
+
+ if (talk._talkToAbort)
+ return 1;
+
+ // Add new anim shape entry for displaying the animation
+ _canimShapes.push_back(Object());
+ Object &cObj = _canimShapes[_canimShapes.size() - 1];
+
+ // Copy the canimation into the bgShapes type canimation structure so it can be played
+ cObj._allow = cAnimNum + 1; // Keep track of the parent structure
+ cObj._name = _cAnim[cAnimNum]._name; // Copy name
+
+ // Remove any attempt to draw object frame
+ if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100)
+ cAnim._sequences[0] = 0;
+
+ cObj._sequences = cAnim._sequences;
+ cObj._images = nullptr;
+ cObj._position = cAnim._position;
+ cObj._delta = Common::Point(0, 0);
+ cObj._type = cAnim._type;
+ cObj._flags = cAnim._flags;
+
+ cObj._maxFrames = 0;
+ cObj._frameNumber = -1;
+ cObj._sequenceNumber = cAnimNum;
+ cObj._oldPosition = Common::Point(0, 0);
+ cObj._oldSize = Common::Point(0, 0);
+ cObj._goto = Common::Point(0, 0);
+ cObj._status = 0;
+ cObj._misc = 0;
+ cObj._imageFrame = nullptr;
+
+ if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) {
+ if (tpPos.x != -1)
+ people[HOLMES]._type = REMOVE;
+
+ Common::String fname = cAnim._name + ".vgs";
+ if (!res.isInCache(fname)) {
+ // Set up RRM scene data
+ Common::SeekableReadStream *roomStream = res.load(_roomFilename);
+ roomStream->seek(cAnim._dataOffset);
+ //rrmStream->seek(44 + cAnimNum * 4);
+ //rrmStream->seek(rrmStream->readUint32LE());
+
+ // Load the canimation into the cache
+ Common::SeekableReadStream *imgStream = !_compressed ? roomStream->readStream(cAnim._dataSize) :
+ Resources::decompressLZ(*roomStream, cAnim._dataSize);
+ res.addToCache(fname, *imgStream);
+
+ delete imgStream;
+ delete roomStream;
+ }
+
+ // Now load the resource as an image
+ if (!IS_3DO) {
+ cObj._images = new ImageFile(fname);
+ } else {
+ cObj._images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat);
+ }
+ cObj._imageFrame = &(*cObj._images)[0];
+ cObj._maxFrames = cObj._images->size();
+
+ int frames = 0;
+ if (playRate < 0) {
+ // Reverse direction
+ // Count number of frames
+ while (frames < MAX_FRAME && cObj._sequences[frames])
+ ++frames;
+ } else {
+ // Forward direction
+ BaseObject::_countCAnimFrames = true;
+
+ while (cObj._type == ACTIVE_BG_SHAPE) {
+ cObj.checkObject();
+ ++frames;
+
+ if (frames >= 1000)
+ error("CAnim has infinite loop sequence");
+ }
+
+ if (frames > 1)
+ --frames;
+
+ BaseObject::_countCAnimFrames = false;
+
+ cObj._type = cAnim._type;
+ cObj._frameNumber = -1;
+ cObj._position = cAnim._position;
+ cObj._delta = Common::Point(0, 0);
+ }
+
+ // Return if animation has no frames in it
+ if (frames == 0)
+ return -2;
+
+ ++frames;
+ int repeat = ABS(playRate);
+ int dir;
+
+ if (playRate < 0) {
+ // Play in reverse
+ dir = -2;
+ cObj._frameNumber = frames - 3;
+ } else {
+ dir = 0;
+ }
+
+ tFrames = frames - 1;
+ int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1;
+
+ while (--frames) {
+ if (frames == pauseFrame)
+ ui.printObjectDesc();
+
+ doBgAnim();
+
+ // Repeat same frame
+ int temp = repeat;
+ while (--temp > 0) {
+ cObj._frameNumber--;
+ doBgAnim();
+
+ if (_vm->shouldQuit())
+ return 0;
+ }
+
+ cObj._frameNumber += dir;
+ }
+
+ people[HOLMES]._type = CHARACTER;
+ }
+
+ // Teleport to ending coordinates if necessary
+ if (tpPos.x != -1) {
+ people[HOLMES]._position = tpPos; // Place the player
+ people[HOLMES]._sequenceNumber = tpDir;
+ people[HOLMES].gotoStand();
+ }
+
+ if (playRate < 0)
+ // Reverse direction - set to end sequence
+ cObj._frameNumber = tFrames - 1;
+
+ if (cObj._frameNumber <= 26)
+ gotoCode = cObj._sequences[cObj._frameNumber + 3];
+
+ // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it
+ if (_canimShapes.indexOf(cObj) != -1)
+ cObj.checkObject();
+
+ if (gotoCode > 0 && !talk._talkToAbort) {
+ _goToScene = gotoCode;
+
+ if (_goToScene < 97 && map[_goToScene].x) {
+ map._overPos = map[_goToScene];
+ }
+ }
+
+ people.loadWalk();
+
+ if (tpPos.x != -1 && !talk._talkToAbort) {
+ // Teleport to ending coordinates
+ people[HOLMES]._position = tpPos;
+ people[HOLMES]._sequenceNumber = tpDir;
+
+ people[HOLMES].gotoStand();
+ }
+
+ events.setCursor(oldCursor);
+
+ return 1;
+}
+
+int ScalpelScene::closestZone(const Common::Point &pt) {
+ int dist = 1000;
+ int zone = -1;
+
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ Common::Point zc((_zones[idx].left + _zones[idx].right) / 2,
+ (_zones[idx].top + _zones[idx].bottom) / 2);
+ int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y);
+
+ if (d < dist) {
+ // Found a closer zone
+ dist = d;
+ zone = idx;
+ }
+ }
+
+ return zone;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_scene.h b/engines/sherlock/scalpel/scalpel_scene.h
new file mode 100644
index 0000000000..fa65ecd95b
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_scene.h
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_SCENE_H
+#define SHERLOCK_SCALPEL_SCENE_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "sherlock/objects.h"
+#include "sherlock/scene.h"
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19,
+ LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55,
+ BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 };
+
+class ScalpelScene : public Scene {
+private:
+ void doBgAnimCheckCursor();
+protected:
+ /**
+ * Loads the data associated for a given scene. The room resource file's format is:
+ * BGHEADER: Holds an index for the rest of the file
+ * STRUCTS: The objects for the scene
+ * IMAGES: The graphic information for the structures
+ *
+ * The _misc field of the structures contains the number of the graphic image
+ * that it should point to after loading; _misc is then set to 0.
+ */
+ virtual bool loadScene(const Common::String &filename);
+
+ /**
+ * Checks all the background shapes. If a background shape is animating,
+ * it will flag it as needing to be drawn. If a non-animating shape is
+ * colliding with another shape, it will also flag it as needing drawing
+ */
+ virtual void checkBgShapes();
+
+ /**
+ * Draw all the shapes, people and NPCs in the correct order
+ */
+ virtual void drawAllShapes();
+
+ /**
+ * Returns the index of the closest zone to a given point.
+ */
+ virtual int closestZone(const Common::Point &pt);
+public:
+ ScalpelScene(SherlockEngine *vm) : Scene(vm) {}
+
+ /**
+ * Draw all objects and characters.
+ */
+ virtual void doBgAnim();
+
+ /**
+ * Attempt to start a canimation sequence. It will load the requisite graphics, and
+ * then copy the canim object into the _canimShapes array to start the animation.
+ *
+ * @param cAnimNum The canim object within the current scene
+ * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc.
+ * A negative playRate can also be specified to play the animation in reverse
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp
new file mode 100644
index 0000000000..2096dabcdf
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_screen.cpp
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) {
+}
+
+void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX,
+ const Common::String &str) {
+
+ Surface &bb = *_backBuffer;
+ bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP);
+ bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP);
+ bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM);
+ bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM);
+ bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE);
+
+ gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ gPrint(Common::Point(textX + charWidth(str[0]), bounds.top),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+}
+
+void ScalpelScreen::buttonPrint(const Common::Point &pt, byte color, bool slamIt,
+ const Common::String &str) {
+ int xStart = pt.x - stringWidth(str) / 2;
+
+ if (color == COMMAND_FOREGROUND) {
+ // First character needs to be highlighted
+ if (slamIt) {
+ print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ print(Common::Point(xStart + charWidth(str[0]), pt.y + 1),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+ } else {
+ gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]);
+ gPrint(Common::Point(xStart + charWidth(str[0]), pt.y),
+ COMMAND_FOREGROUND, "%s", str.c_str() + 1);
+ }
+ } else if (slamIt) {
+ print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str());
+ } else {
+ gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str());
+ }
+}
+
+void ScalpelScreen::makePanel(const Common::Rect &r) {
+ _backBuffer->fillRect(r, BUTTON_MIDDLE);
+ _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP);
+ _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP);
+ _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP);
+ _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
+
+ _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM);
+ _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM);
+}
+
+void ScalpelScreen::makeField(const Common::Rect &r) {
+ _backBuffer->fillRect(r, BUTTON_MIDDLE);
+ _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM);
+ _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP);
+ _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM);
+ _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP);
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_screen.h b/engines/sherlock/scalpel/scalpel_screen.h
new file mode 100644
index 0000000000..472fe9e220
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_screen.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_SCREEN_H
+#define SHERLOCK_SCALPEL_SCREEN_H
+
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+class ScalpelScreen : public Screen {
+public:
+ ScalpelScreen(SherlockEngine *vm);
+ virtual ~ScalpelScreen() {}
+
+ /**
+ * Draws a button for use in the inventory, talk, and examine dialogs.
+ */
+ void makeButton(const Common::Rect &bounds, int textX, const Common::String &str);
+
+ /**
+ * Prints an interface command with the first letter highlighted to indicate
+ * what keyboard shortcut is associated with it
+ */
+ void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str);
+
+ /**
+ * Draw a panel in the back buffer with a raised area effect around the edges
+ */
+ void makePanel(const Common::Rect &r);
+
+ /**
+ * Draw a field in the back buffer with a raised area effect around the edges,
+ * suitable for text input.
+ */
+ void makeField(const Common::Rect &r);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp
new file mode 100644
index 0000000000..aa0a2f48b4
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_talk.cpp
@@ -0,0 +1,844 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_talk.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_map.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/screen.h"
+#include "sherlock/scalpel/3do/movie_decoder.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+const byte SCALPEL_OPCODES[] = {
+ 128, // OP_SWITCH_SPEAKER
+ 129, // OP_RUN_CANIMATION
+ 130, // OP_ASSIGN_PORTRAIT_LOCATION
+ 131, // OP_PAUSE
+ 132, // OP_REMOVE_PORTRAIT
+ 133, // OP_CLEAR_WINDOW
+ 134, // OP_ADJUST_OBJ_SEQUENCE
+ 135, // OP_WALK_TO_COORDS
+ 136, // OP_PAUSE_WITHOUT_CONTROL
+ 137, // OP_BANISH_WINDOW
+ 138, // OP_SUMMON_WINDOW
+ 139, // OP_SET_FLAG
+ 140, // OP_SFX_COMMAND
+ 141, // OP_TOGGLE_OBJECT
+ 142, // OP_STEALTH_MODE_ACTIVE
+ 143, // OP_IF_STATEMENT
+ 144, // OP_ELSE_STATEMENT
+ 145, // OP_END_IF_STATEMENT
+ 146, // OP_STEALTH_MODE_DEACTIVATE
+ 147, // OP_TURN_HOLMES_OFF
+ 148, // OP_TURN_HOLMES_ON
+ 149, // OP_GOTO_SCENE
+ 150, // OP_PLAY_PROLOGUE
+ 151, // OP_ADD_ITEM_TO_INVENTORY
+ 152, // OP_SET_OBJECT
+ 153, // OP_CALL_TALK_FILE
+ 143, // OP_MOVE_MOUSE
+ 155, // OP_DISPLAY_INFO_LINE
+ 156, // OP_CLEAR_INFO_LINE
+ 157, // OP_WALK_TO_CANIMATION
+ 158, // OP_REMOVE_ITEM_FROM_INVENTORY
+ 159, // OP_ENABLE_END_KEY
+ 160, // OP_DISABLE_END_KEY
+ 161, // OP_END_TEXT_WINDOW
+ 0, // OP_MOUSE_ON_OFF
+ 0, // OP_SET_WALK_CONTROL
+ 0, // OP_SET_TALK_SEQUENCE
+ 0, // OP_PLAY_SONG
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
+ 0, // OP_SET_NPC_PATH_DEST
+ 0, // OP_NEXT_SONG
+ 0, // OP_SET_NPC_PATH_PAUSE
+ 0, // OP_PASSWORD
+ 0, // OP_SET_SCENE_ENTRY_FLAG
+ 0, // OP_WALK_NPC_TO_CANIM
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 0, // OP_SET_NPC_TALK_FILE
+ 0, // OP_TURN_NPC_OFF
+ 0, // OP_TURN_NPC_ON
+ 0, // OP_NPC_DESC_ON_OFF
+ 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES
+ 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
+ 0, // OP_ENABLE_TALK_INTERRUPTS
+ 0, // OP_DISABLE_TALK_INTERRUPTS
+ 0, // OP_SET_NPC_INFO_LINE
+ 0, // OP_SET_NPC_POSITION
+ 0, // OP_NPC_PATH_LABEL
+ 0, // OP_PATH_GOTO_LABEL
+ 0, // OP_PATH_IF_FLAG_GOTO_LABEL
+ 0, // OP_NPC_WALK_GRAPHICS
+ 0, // OP_NPC_VERB
+ 0, // OP_NPC_VERB_CANIM
+ 0, // OP_NPC_VERB_SCRIPT
+ 0, // OP_RESTORE_PEOPLE_SEQUENCE
+ 0, // OP_NPC_VERB_TARGET
+ 0, // OP_TURN_SOUNDS_OFF
+ 0 // OP_NULL
+};
+
+/*----------------------------------------------------------------*/
+
+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::cmdEndTextWindow,
+ 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, 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
+ };
+
+ _opcodeTable = OPCODE_METHODS;
+ _opcodes = SCALPEL_OPCODES;
+
+ if (vm->getLanguage() == Common::DE_DEU || vm->getLanguage() == Common::ES_ESP) {
+ // The German and Spanish versions use a different opcode range
+ static byte opcodes[sizeof(SCALPEL_OPCODES)];
+ for (uint idx = 0; idx < sizeof(SCALPEL_OPCODES); ++idx)
+ opcodes[idx] = SCALPEL_OPCODES[idx] ? SCALPEL_OPCODES[idx] + 47 : 0;
+
+ _opcodes = opcodes;
+ }
+
+}
+
+void ScalpelTalk::talkInterface(const byte *&str) {
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ // If the window isn't yet open, draw the window before printing starts
+ if (!ui._windowOpen && _noTextYet) {
+ _noTextYet = false;
+ drawInterface();
+
+ if (_talkTo != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ }
+ }
+
+ // If it's the first line, display the speaker
+ 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",
+ people._characters[_speaker & 127]._name);
+ }
+ else {
+ screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s",
+ people._characters[_speaker & 127]._name);
+ _openTalkWindow = true;
+ }
+
+ _yp += 9;
+ }
+
+ // Find amount of text that will fit on the line
+ int width = 0, idx = 0;
+ do {
+ width += screen.charWidth(str[idx]);
+ ++idx;
+ ++_charCount;
+ } while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx])));
+
+ if (str[idx] || width >= 298) {
+ if ((!isOpcode(str[idx])) && 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((const char *)str, (const char *)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), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ }
+ else {
+ 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());
+ }
+ else {
+ screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str());
+ _openTalkWindow = true;
+ }
+ }
+
+ // Move to end of displayed line
+ str += idx;
+
+ // If line wrap occurred, then move to after the separating space between the words
+ if ((!isOpcode(str[0])) && str[0] != '{')
+ ++str;
+
+ _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;
+ }
+
+ byte v = (str >= _scriptEnd ? 0 : str[0]);
+ if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] ||
+ v == _opcodes[OP_BANISH_WINDOW] || v == _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]) {
+ _wait = 1;
+ }
+}
+
+OpcodeReturn ScalpelTalk::cmdSwitchSpeaker(const byte *&str) {
+ ScalpelPeople &people = *(ScalpelPeople *)_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);
+ people.setTalkSequence(_speaker);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn ScalpelTalk::cmdGotoScene(const byte *&str) {
+ ScalpelMap &map = *(ScalpelMap *)_vm->_map;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ scene._goToScene = str[1] - 1;
+
+ if (scene._goToScene != OVERHEAD_MAP) {
+ // Not going to the map overview
+ map._oldCharPoint = scene._goToScene;
+ map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
+ map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
+
+ // Run a canimation?
+ if (str[2] > 100) {
+ people._savedPos = PositionFacing(160, 100, str[2]);
+ } else {
+ int32 posX = (str[3] - 1) * 256 + str[4] - 1;
+ int32 posY = str[5] - 1;
+ people._savedPos = PositionFacing(posX, posY, str[2] - 1);
+ }
+ }
+
+ str += 6;
+
+ _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1;
+ _scriptSaveIndex = str - _scriptStart;
+ _endStr = true;
+ _wait = 0;
+
+ return RET_SUCCESS;
+}
+
+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::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, false, 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::cmdWalkToCoords(const byte *&str) {
+ People &people = *_vm->_people;
+ ++str;
+
+ people[HOLMES].walkToCoords(Point32(((str[0] - 1) * 256 + str[1] - 1) * FIXED_INT_MULTIPLIER,
+ str[2] * FIXED_INT_MULTIPLIER), str[3] - 1);
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 3;
+ 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;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+
+ drawInterface();
+ events._pressed = events._released = false;
+ events.clearKeyboard();
+ _noTextYet = false;
+
+ if (_speaker != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ }
+
+ return RET_SUCCESS;
+}
+
+void ScalpelTalk::talkWait(const byte *&str) {
+ UserInterface &ui = *_vm->_ui;
+ bool pauseFlag = _pauseFlag;
+
+ Talk::talkWait(str);
+
+ // 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 (!_talkStealth)
+ ui.clearWindow();
+ _yp = CONTROLS_Y + 12;
+ _charCount = _line = 0;
+ }
+}
+
+void ScalpelTalk::talk3DOMovieTrigger(int subIndex) {
+ if (!IS_3DO) {
+ // No 3DO? No movie!
+ return;
+ }
+
+ // Find out a few things that we need
+ int userSelector = _vm->_ui->_selector;
+ int scriptSelector = _scriptSelect;
+ int selector = 0;
+ int roomNr = _vm->_scene->_currentScene;
+
+ if (userSelector >= 0) {
+ // User-selected dialog
+ selector = userSelector;
+ } else {
+ if (scriptSelector >= 0) {
+ // Script-selected dialog
+ selector = scriptSelector;
+ subIndex--; // for scripts we adjust subIndex, b/c we won't get called from doTalkControl()
+ } else {
+ warning("talk3DOMovieTrigger: unable to find selector");
+ return;
+ }
+ }
+
+ // Make a quick update, so that current text is shown on screen
+ _vm->_screen->update();
+
+ // Figure out that movie filename
+ Common::String movieFilename;
+
+ movieFilename = _scriptName;
+ movieFilename.deleteChar(1); // remove 2nd character of scriptname
+ // cut scriptname to 6 characters
+ while (movieFilename.size() > 6) {
+ movieFilename.deleteChar(6);
+ }
+
+ movieFilename.insertChar(selector + 'a', movieFilename.size());
+ movieFilename.insertChar(subIndex + 'a', movieFilename.size());
+ movieFilename = Common::String::format("movies/%02d/%s.stream", roomNr, movieFilename.c_str());
+
+ warning("3DO movie player:");
+ warning("room: %d", roomNr);
+ warning("script: %s", _scriptName.c_str());
+ warning("selector: %d", selector);
+ warning("subindex: %d", subIndex);
+
+ Scalpel3DOMoviePlay(movieFilename.c_str(), Common::Point(5, 5));
+
+ // Restore screen HACK
+ _vm->_screen->makeAllDirty();
+}
+
+void ScalpelTalk::drawInterface() {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Surface &bb = *screen._backBuffer;
+
+ bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+
+ if (_talkTo != -1) {
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+
+ screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10),
+ 119 - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit);
+ screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10),
+ 159 - screen.stringWidth(fixedText_Up) / 2, fixedText_Up);
+ screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10),
+ 200 - screen.stringWidth(fixedText_Down) / 2, fixedText_Down);
+ } else {
+ int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE);
+ screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10),
+ 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE);
+ screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P");
+ }
+}
+
+bool ScalpelTalk::displayTalk(bool slamIt) {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ int yp = CONTROLS_Y + 14;
+ int lineY = -1;
+ _moreTalkDown = _moreTalkUp = false;
+
+ for (uint idx = 0; idx < _statements.size(); ++idx) {
+ _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1;
+ }
+
+ if (_talkIndex) {
+ for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) {
+ if (_statements[idx]._talkMap != -1)
+ _moreTalkUp = true;
+ }
+ }
+
+ // Display the up arrow and enable Up button if the first option is scrolled off-screen
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+ if (_moreTalkUp) {
+ if (slamIt) {
+ screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
+ } else {
+ screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~");
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Up);
+ }
+ } else {
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up);
+ screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND);
+ } else {
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up);
+ screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11,
+ 15, CONTROLS_Y + 22), INV_BACKGROUND);
+ }
+ }
+
+ // Loop through the statements
+ bool done = false;
+ for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) {
+ Statement &statement = _statements[idx];
+
+ if (statement._talkMap != -1) {
+ bool flag = _talkHistory[_converseNum][idx];
+ lineY = talkLine(idx, statement._talkMap, flag ? (byte)TALK_NULL : (byte)INV_FOREGROUND,
+ yp, slamIt);
+
+ if (lineY != -1) {
+ statement._talkPos.top = yp;
+ yp = lineY;
+ statement._talkPos.bottom = yp;
+
+ if (yp == SHERLOCK_SCREEN_HEIGHT)
+ done = true;
+ } else {
+ done = true;
+ }
+ }
+ }
+
+ // Display the down arrow and enable down button if there are more statements available down off-screen
+ if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) {
+ _moreTalkDown = true;
+
+ if (slamIt) {
+ screen.print(Common::Point(5, 190), INV_FOREGROUND, "|");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
+ } else {
+ screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|");
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Down);
+ }
+ } else {
+ if (slamIt) {
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down);
+ screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
+ } else {
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down);
+ screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND);
+ }
+ }
+
+ return done;
+}
+
+int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) {
+ Screen &screen = *_vm->_screen;
+ int idx = lineNum;
+ Common::String msg, number;
+ bool numberFlag = false;
+
+ // Get the statement to display as well as optional number prefix
+ if (idx < SPEAKER_REMOVE) {
+ number = Common::String::format("%d.", stateNum + 1);
+ numberFlag = true;
+ } else {
+ idx -= SPEAKER_REMOVE;
+ }
+ msg = _statements[idx]._statement;
+
+ // Handle potentially multiple lines needed to display entire statement
+ const char *lineStartP = msg.c_str();
+ int maxWidth = 298 - (numberFlag ? 18 : 0);
+ for (;;) {
+ // Get as much of the statement as possible will fit on the
+ Common::String sLine;
+ const char *lineEndP = lineStartP;
+ int width = 0;
+ do {
+ width += screen.charWidth(*lineEndP);
+ } while (*++lineEndP && width < maxWidth);
+
+ // Check if we need to wrap the line
+ if (width >= maxWidth) {
+ // Work backwards to the prior word's end
+ while (*--lineEndP != ' ')
+ ;
+
+ sLine = Common::String(lineStartP, lineEndP++);
+ } else {
+ // Can display remainder of the statement on the current line
+ sLine = Common::String(lineStartP);
+ }
+
+
+ if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) {
+ // Need to directly display on-screen?
+ if (slamIt) {
+ // See if a numer prefix is needed or not
+ if (numberFlag) {
+ // Are we drawing the first line?
+ if (lineStartP == msg.c_str()) {
+ // We are, so print the number and then the text
+ screen.print(Common::Point(16, lineY), color, "%s", number.c_str());
+ }
+
+ // Draw the line with an indent
+ screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str());
+ } else {
+ screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str());
+ }
+ } else {
+ if (numberFlag) {
+ if (lineStartP == msg.c_str()) {
+ screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str());
+ }
+
+ screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str());
+ } else {
+ screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str());
+ }
+ }
+
+ // Move to next line, if any
+ lineY += 9;
+ lineStartP = lineEndP;
+
+ if (!*lineEndP)
+ break;
+ } else {
+ // We're close to the bottom of the screen, so stop display
+ lineY = -1;
+ break;
+ }
+ }
+
+ if (lineY == -1 && lineStartP != msg.c_str())
+ lineY = SHERLOCK_SCREEN_HEIGHT;
+
+ // Return the Y position of the next line to follow this one
+ return lineY;
+}
+
+void ScalpelTalk::showTalk() {
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL;
+
+ clearSequences();
+ pushSequence(_talkTo);
+ setStillSeq(_talkTo);
+
+ ui._selector = ui._oldSelector = -1;
+
+ if (!ui._windowOpen) {
+ // Draw the talk interface on the back buffer
+ drawInterface();
+ displayTalk(false);
+ } else {
+ displayTalk(true);
+ }
+
+ // If the window is already open, simply draw. Otherwise, do it
+ // to the back buffer and then summon the window
+ if (ui._windowOpen) {
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, fixedText_Exit);
+ } else {
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, fixedText_Exit);
+
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow();
+ }
+
+ ui._windowOpen = true;
+ }
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_talk.h b/engines/sherlock/scalpel/scalpel_talk.h
new file mode 100644
index 0000000000..24188d8fcd
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_talk.h
@@ -0,0 +1,99 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_TALK_H
+#define SHERLOCK_SCALPEL_TALK_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "common/stream.h"
+#include "common/stack.h"
+#include "sherlock/talk.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+class ScalpelTalk : public Talk {
+private:
+ OpcodeReturn cmdSwitchSpeaker(const byte *&str);
+ OpcodeReturn cmdAssignPortraitLocation(const byte *&str);
+ OpcodeReturn cmdGotoScene(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 cmdWalkToCoords(const byte *&str);
+protected:
+ /**
+ * Display the talk interface window
+ */
+ virtual void talkInterface(const byte *&str);
+
+ /**
+ * Pause when displaying a talk dialog on-screen
+ */
+ virtual void talkWait(const byte *&str);
+
+ /**
+ * Trigger to play a 3DO talk dialog movie
+ */
+ virtual void talk3DOMovieTrigger(int subIndex);
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk();
+public:
+ ScalpelTalk(SherlockEngine *vm);
+ virtual ~ScalpelTalk() {}
+
+ /**
+ * Draws the interface for conversation display
+ */
+ void drawInterface();
+
+ /**
+ * Display a list of statements in a window at the bottom of the screen that the
+ * player can select from.
+ */
+ bool displayTalk(bool slamIt);
+
+ /**
+ * Prints a single conversation option in the interface window
+ */
+ int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp
new file mode 100644
index 0000000000..8fcc92b2ac
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp
@@ -0,0 +1,2189 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel_fixed_text.h"
+#include "sherlock/scalpel/scalpel_inventory.h"
+#include "sherlock/scalpel/scalpel_journal.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_saveload.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/settings.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+// Main user interface menu control locations
+const int MENU_POINTS[12][4] = {
+ { 13, 153, 72, 165 },
+ { 13, 169, 72, 181 },
+ { 13, 185, 72, 197 },
+ { 88, 153, 152, 165 },
+ { 88, 169, 152, 181 },
+ { 88, 185, 152, 197 },
+ { 165, 153, 232, 165 },
+ { 165, 169, 232, 181 },
+ { 165, 185, 233, 197 },
+ { 249, 153, 305, 165 },
+ { 249, 169, 305, 181 },
+ { 249, 185, 305, 197 }
+};
+
+// Inventory control locations */
+const int INVENTORY_POINTS[8][3] = {
+ { 4, 50, 29 },
+ { 52, 99, 77 },
+ { 101, 140, 123 },
+ { 142, 187, 166 },
+ { 189, 219, 198 },
+ { 221, 251, 234 },
+ { 253, 283, 266 },
+ { 285, 315, 294 }
+};
+
+const char COMMANDS[13] = "LMTPOCIUGJFS";
+const char INVENTORY_COMMANDS[9] = { "ELUG-+,." };
+const char *const PRESS_KEY_FOR_MORE = "Press any Key for More.";
+const char *const PRESS_KEY_TO_CONTINUE = "Press any Key to Continue.";
+const int UI_OFFSET_3DO = 16; // (320 - 288) / 2
+
+/*----------------------------------------------------------------*/
+
+
+ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) {
+ if (_vm->_interactiveFl) {
+ if (!IS_3DO) {
+ // PC
+ _controls = new ImageFile("menu.all");
+ _controlPanel = new ImageFile("controls.vgs");
+ } else {
+ // 3DO
+ _controls = new ImageFile3DO("menu.all", kImageFile3DOType_RoomFormat);
+ _controlPanel = new ImageFile3DO("controls.vgs", kImageFile3DOType_RoomFormat);
+ }
+ } else {
+ _controls = nullptr;
+ _controlPanel = nullptr;
+ }
+
+ _keyPress = '\0';
+ _lookHelp = 0;
+ _help = _oldHelp = 0;
+ _key = _oldKey = '\0';
+ _temp = _oldTemp = 0;
+ _oldLook = 0;
+ _keyboardInput = false;
+ _pause = false;
+ _cNum = 0;
+ _find = 0;
+ _oldUse = 0;
+}
+
+ScalpelUserInterface::~ScalpelUserInterface() {
+ delete _controls;
+ delete _controlPanel;
+}
+
+void ScalpelUserInterface::reset() {
+ UserInterface::reset();
+ _help = _oldHelp = -1;
+}
+
+void ScalpelUserInterface::drawInterface(int bufferNum) {
+ Screen &screen = *_vm->_screen;
+
+ const ImageFrame &src = (*_controlPanel)[0];
+ int16 x = (!IS_3DO) ? 0 : UI_OFFSET_3DO;
+
+ if (bufferNum & 1)
+ screen._backBuffer1.transBlitFrom(src, Common::Point(x, CONTROLS_Y));
+ if (bufferNum & 2)
+ screen._backBuffer2.transBlitFrom(src, Common::Point(x, CONTROLS_Y));
+ if (bufferNum == 3)
+ screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK);
+}
+
+void ScalpelUserInterface::handleInput() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+
+ if (_menuCounter)
+ whileMenuCounter();
+
+ Common::Point pt = events.mousePos();
+ _bgFound = scene.findBgShape(pt);
+ _keyPress = '\0';
+
+ // Check kbd and set the mouse released flag if Enter or space is pressed.
+ // Otherwise, the pressed _key is stored for later use
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ _keyPress = keyState.ascii;
+
+ if (keyState.keycode == Common::KEYCODE_x && keyState.flags & Common::KBD_ALT) {
+ _vm->quitGame();
+ events.pollEvents();
+ return;
+ }
+ }
+
+ // Do button highlighting check
+ 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
+ if (_menuMode == STD_MODE) {
+ if (pt.y < CONTROLS_Y &&
+ (events._rightPressed || (!_helpStyle && !events._released)) &&
+ (_bgFound != -1) && (_bgFound < 1000) &&
+ (scene._bgShapes[_bgFound]._defaultCommand ||
+ !scene._bgShapes[_bgFound]._description.empty())) {
+ // If there is no default command, so set it to Look
+ if (scene._bgShapes[_bgFound]._defaultCommand)
+ _help = scene._bgShapes[_bgFound]._defaultCommand - 1;
+ else
+ _help = 0;
+
+ // Reset 'help' if it is an invalid command
+ if (_help > 5)
+ _help = -1;
+ } else if (pt.y < CONTROLS_Y &&
+ ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) &&
+ (_bgFound != -1 && _bgFound < 1000) &&
+ (scene._bgShapes[_bgFound]._defaultCommand ||
+ !scene._bgShapes[_bgFound]._description.empty())) {
+ // If there is no default command, set it to Look
+ if (scene._bgShapes[_bgFound]._defaultCommand)
+ _menuMode = (MenuMode)scene._bgShapes[_bgFound]._defaultCommand;
+ else
+ _menuMode = LOOK_MODE;
+ events._released = true;
+ events._pressed = events._oldButtons = false;
+ _help = _oldHelp = -1;
+
+ if (_menuMode == LOOK_MODE) {
+ // Set the flag to tell the game that this was a right-click
+ // call to look and should exit without the look button being pressed
+ _lookHelp = true;
+ }
+ } else {
+ _help = -1;
+ }
+
+ // Check if highlighting a different button than last time
+ if (_help != _oldHelp) {
+ // If another button was highlighted previously, restore it
+ if (_oldHelp != -1)
+ restoreButton(_oldHelp);
+
+ // If we're highlighting a new button, then draw it pressed
+ if (_help != -1)
+ depressButton(_help);
+
+ _oldHelp = _help;
+ }
+
+ if (_bgFound != _oldBgFound || _oldBgFound == -1) {
+ _infoFlag = true;
+ clearInfo();
+
+ if (_help != -1 && !scene._bgShapes[_bgFound]._description.empty()
+ && scene._bgShapes[_bgFound]._description[0] != ' ')
+ screen.print(Common::Point(0, INFO_LINE + 1),
+ INFO_FOREGROUND, "%s", scene._bgShapes[_bgFound]._description.c_str());
+
+ _oldBgFound = _bgFound;
+ }
+ } else {
+ // We're not in STD_MODE
+ // If there isn't a window open, then revert back to STD_MODE
+ if (!_windowOpen && events._rightReleased) {
+ // Restore all buttons
+ for (int idx = 0; idx < 12; ++idx)
+ restoreButton(idx);
+
+ _menuMode = STD_MODE;
+ _key = _oldKey = -1;
+ _temp = _oldTemp = _lookHelp = _invLookFlag = 0;
+ events.clearEvents();
+ }
+ }
+ }
+ }
+
+ // Reset the old bgshape number if the mouse button is released, so that
+ // it can e re-highlighted when we come back here
+ if ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle))
+ _oldBgFound = -1;
+
+ // Do routines that should be done before input processing
+ switch (_menuMode) {
+ case LOOK_MODE:
+ if (!_windowOpen) {
+ if (events._released && _bgFound >= 0 && _bgFound < 1000) {
+ if (!scene._bgShapes[_bgFound]._examine.empty())
+ examine();
+ } else {
+ lookScreen(pt);
+ }
+ }
+ break;
+
+ case MOVE_MODE:
+ case OPEN_MODE:
+ case CLOSE_MODE:
+ case PICKUP_MODE:
+ lookScreen(pt);
+ break;
+
+ case TALK_MODE:
+ if (!_windowOpen) {
+ bool personFound;
+
+ if (_bgFound >= 1000) {
+ personFound = false;
+ if (!events._released)
+ lookScreen(pt);
+ } else {
+ personFound = _bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON;
+ }
+
+ if (events._released && personFound)
+ talk.talk(_bgFound);
+ else if (personFound)
+ lookScreen(pt);
+ else if (_bgFound < 1000)
+ clearInfo();
+ }
+ break;
+
+ case USE_MODE:
+ case GIVE_MODE:
+ case INV_MODE:
+ if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) {
+ if (pt.y > CONTROLS_Y)
+ lookInv();
+ else
+ lookScreen(pt);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ //
+ // Do input processing
+ //
+ if (events._pressed || events._released || events._rightPressed || _keyPress || _pause) {
+ if (((events._released && (_helpStyle || _help == -1)) || (events._rightReleased && !_helpStyle)) &&
+ (pt.y <= CONTROLS_Y) && (_menuMode == STD_MODE)) {
+ // The mouse was clicked in the playing area with no action buttons down.
+ // Check if the mouse was clicked in a script zone. If it was,
+ // then execute the script. Otherwise, walk to the given position
+ if (scene.checkForZones(pt, SCRIPT_ZONE) != 0 ||
+ scene.checkForZones(pt, NOWALK_ZONE) != 0) {
+ // Mouse clicked in script zone
+ events._pressed = events._released = false;
+ } else {
+ people._allowWalkAbort = false;
+ people[HOLMES]._walkDest = pt;
+ people[HOLMES].goAllTheWay();
+ }
+
+ if (_oldKey != -1) {
+ restoreButton(_oldTemp);
+ _oldKey = -1;
+ }
+ }
+
+ // Handle action depending on selected mode
+ switch (_menuMode) {
+ case LOOK_MODE:
+ if (_windowOpen)
+ doLookControl();
+ break;
+
+ case MOVE_MODE:
+ doMiscControl(ALLOW_MOVE);
+ break;
+
+ case TALK_MODE:
+ if (_windowOpen)
+ doTalkControl();
+ break;
+
+ case OPEN_MODE:
+ doMiscControl(ALLOW_OPEN);
+ break;
+
+ case CLOSE_MODE:
+ doMiscControl(ALLOW_CLOSE);
+ break;
+
+ case PICKUP_MODE:
+ doPickControl();
+ break;
+
+ case USE_MODE:
+ case GIVE_MODE:
+ case INV_MODE:
+ doInvControl();
+ break;
+
+ case FILES_MODE:
+ doEnvControl();
+ break;
+
+ default:
+ break;
+ }
+
+ // As long as there isn't an open window, do main input processing.
+ // Windows are opened when in TALK, USE, INV, and GIVE modes
+ if ((!_windowOpen && !_menuCounter && pt.y > CONTROLS_Y) ||
+ _keyPress) {
+ if (events._pressed || events._released || _pause || _keyPress)
+ doMainControl();
+ }
+
+ if (pt.y < CONTROLS_Y && events._pressed && _oldTemp != (int)(_menuMode - 1) && _oldKey != -1)
+ restoreButton(_oldTemp);
+ }
+}
+
+void ScalpelUserInterface::depressButton(int num) {
+ Screen &screen = *_vm->_screen;
+ Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
+
+ ImageFrame &frame = (*_controls)[num];
+ screen._backBuffer1.transBlitFrom(frame, pt);
+ screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height);
+}
+
+void ScalpelUserInterface::restoreButton(int num) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
+
+ Graphics::Surface &frame = (*_controls)[num]._frame;
+
+ // Reset the cursor
+ events.setCursor(ARROW);
+
+ // Restore the UI on the back buffer
+ screen._backBuffer1.blitFrom(screen._backBuffer2, pt,
+ Common::Rect(pt.x, pt.y, pt.x + 90, pt.y + 19));
+ screen.slamArea(pt.x, pt.y, pt.x + frame.w, pt.y + frame.h);
+
+ if (!_menuCounter) {
+ _infoFlag = true;
+ clearInfo();
+ }
+}
+
+void ScalpelUserInterface::pushButton(int num) {
+ Events &events = *_vm->_events;
+ _oldKey = -1;
+
+ if (!events._released) {
+ if (_oldHelp != -1)
+ restoreButton(_oldHelp);
+ if (_help != -1)
+ restoreButton(_help);
+
+ depressButton(num);
+ events.wait(6);
+ }
+
+ restoreButton(num);
+}
+
+void ScalpelUserInterface::toggleButton(int num) {
+ Screen &screen = *_vm->_screen;
+
+ if (_menuMode != (MenuMode)(num + 1)) {
+ _menuMode = (MenuMode)(num + 1);
+ _oldKey = COMMANDS[num];
+ _oldTemp = num;
+
+ if (_keyboardInput) {
+ if (_oldHelp != -1 && _oldHelp != num)
+ restoreButton(_oldHelp);
+ if (_help != -1 && _help != num)
+ restoreButton(_help);
+
+ _keyboardInput = false;
+
+ ImageFrame &frame = (*_controls)[num];
+ Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]);
+ offsetButton3DO(pt, num);
+ screen._backBuffer1.transBlitFrom(frame, pt);
+ screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height);
+ }
+ } else {
+ _menuMode = STD_MODE;
+ _oldKey = -1;
+ restoreButton(num);
+ }
+}
+
+void ScalpelUserInterface::clearInfo() {
+ if (_infoFlag) {
+ _vm->_screen->vgaBar(Common::Rect(16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - 19,
+ INFO_LINE + 10), INFO_BLACK);
+ _infoFlag = false;
+ _oldLook = -1;
+ }
+}
+
+void ScalpelUserInterface::clearWindow() {
+ if (_windowOpen) {
+ _vm->_screen->vgaBar(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+ }
+}
+
+void ScalpelUserInterface::whileMenuCounter() {
+ if (!(--_menuCounter) || _vm->_events->checkInput()) {
+ _menuCounter = 0;
+ _infoFlag = true;
+ clearInfo();
+ }
+}
+
+void ScalpelUserInterface::examine() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+ Common::Point pt = events.mousePos();
+
+ if (pt.y < (CONTROLS_Y + 9)) {
+ Object &obj = scene._bgShapes[_bgFound];
+
+ if (obj._lookcAnim != 0) {
+ int canimSpeed = ((obj._lookcAnim & 0xe0) >> 5) + 1;
+ scene._cAnimFramePause = obj._lookFrames;
+ _cAnimStr = obj._examine;
+ _cNum = (obj._lookcAnim & 0x1f) - 1;
+
+ scene.startCAnim(_cNum, canimSpeed);
+ } else if (obj._lookPosition.y != 0) {
+ // Need to walk to the object to be examined
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
+ }
+
+ if (!talk._talkToAbort) {
+ _cAnimStr = obj._examine;
+ if (obj._lookFlag)
+ _vm->setFlags(obj._lookFlag);
+ }
+ } else {
+ // Looking at an inventory item
+ _cAnimStr = inv[_selector]._examine;
+ if (inv[_selector]._lookFlag)
+ _vm->setFlags(inv[_selector]._lookFlag);
+ }
+
+ if (_invLookFlag) {
+ // Don't close the inventory window when starting an examine display, since its
+ // window will slide up to replace the inventory display
+ _windowOpen = false;
+ _menuMode = LOOK_MODE;
+ }
+
+ if (!talk._talkToAbort) {
+ if (!scene._cAnimFramePause)
+ printObjectDesc(_cAnimStr, true);
+ else
+ // description was already printed in startCAnimation
+ scene._cAnimFramePause = 0;
+ }
+}
+
+void ScalpelUserInterface::lookScreen(const Common::Point &pt) {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+ int temp;
+ Common::String tempStr;
+
+ // Don't display anything for right button command
+ if ((events._rightPressed || events._rightReleased) && !events._pressed)
+ return;
+
+ if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) {
+ if (temp != _oldLook) {
+ _infoFlag = true;
+ clearInfo();
+
+ if (temp < 1000)
+ tempStr = scene._bgShapes[temp]._description;
+ else
+ tempStr = scene._bgShapes[temp - 1000]._description;
+
+ _infoFlag = true;
+ clearInfo();
+
+ // Only print description if there is one
+ if (!tempStr.empty() && tempStr[0] != ' ') {
+ // If inventory is active and an item is selected for a Use or Give action
+ if ((_menuMode == INV_MODE || _menuMode == USE_MODE || _menuMode == GIVE_MODE) &&
+ (inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE)) {
+ int width1 = 0, width2 = 0;
+ int x, width;
+ if (inv._invMode == INVMODE_USE) {
+ // Using an object
+ x = width = screen.stringWidth("Use ");
+
+ if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON)
+ // It's not a person, so make it lowercase
+ tempStr.setChar(tolower(tempStr[0]), 0);
+
+ x += screen.stringWidth(tempStr);
+
+ // If we're using an inventory object, add in the width
+ // of the object name and the " on "
+ if (_selector != -1) {
+ width1 = screen.stringWidth(inv[_selector]._name);
+ x += width1;
+ width2 = screen.stringWidth(" on ");
+ x += width2;
+ }
+
+ // If the line will be too long, keep cutting off characters
+ // until the string will fit
+ while (x > 280) {
+ x -= screen.charWidth(tempStr.lastChar());
+ tempStr.deleteLastChar();
+ }
+
+ int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2;
+ screen.print(Common::Point(xStart, INFO_LINE + 1),
+ INFO_FOREGROUND, "Use ");
+
+ if (_selector != -1) {
+ screen.print(Common::Point(xStart + width, INFO_LINE + 1),
+ TALK_FOREGROUND, "%s", inv[_selector]._name.c_str());
+ screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1),
+ INFO_FOREGROUND, " on ");
+ screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1),
+ INFO_FOREGROUND, "%s", tempStr.c_str());
+ } else {
+ screen.print(Common::Point(xStart + width, INFO_LINE + 1),
+ INFO_FOREGROUND, "%s", tempStr.c_str());
+ }
+ } else if (temp >= 0 && temp < 1000 && _selector != -1 &&
+ scene._bgShapes[temp]._aType == PERSON) {
+ // Giving an object to a person
+ width1 = screen.stringWidth(inv[_selector]._name);
+ x = width = screen.stringWidth("Give ");
+ x += width1;
+ width2 = screen.stringWidth(" to ");
+ x += width2;
+ x += screen.stringWidth(tempStr);
+
+ // Ensure string will fit on-screen
+ while (x > 280) {
+ x -= screen.charWidth(tempStr.lastChar());
+ tempStr.deleteLastChar();
+ }
+
+ int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2;
+ screen.print(Common::Point(xStart, INFO_LINE + 1),
+ INFO_FOREGROUND, "Give ");
+ screen.print(Common::Point(xStart + width, INFO_LINE + 1),
+ TALK_FOREGROUND, "%s", inv[_selector]._name.c_str());
+ screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1),
+ INFO_FOREGROUND, " to ");
+ screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1),
+ INFO_FOREGROUND, "%s", tempStr.c_str());
+ }
+ } else {
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str());
+ }
+
+ _infoFlag = true;
+ _oldLook = temp;
+ }
+ }
+ } else {
+ clearInfo();
+ }
+}
+
+void ScalpelUserInterface::lookInv() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+
+ if (mousePos.x > 15 && mousePos.x < 314 && mousePos.y > (CONTROLS_Y1 + 11)
+ && mousePos.y < (SHERLOCK_SCREEN_HEIGHT - 2)) {
+ int temp = (mousePos.x - 6) / 52 + inv._invIndex;
+ if (temp < inv._holdings) {
+ if (temp < inv._holdings) {
+ clearInfo();
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND,
+ "%s", inv[temp]._description.c_str());
+ _infoFlag = true;
+ _oldLook = temp;
+ }
+ } else {
+ clearInfo();
+ }
+ } else {
+ clearInfo();
+ }
+}
+
+void ScalpelUserInterface::doEnvControl() {
+ Events &events = *_vm->_events;
+ ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves;
+ Scene &scene = *_vm->_scene;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Common::Point mousePos = events.mousePos();
+ static const char ENV_COMMANDS[7] = "ELSUDQ";
+
+ byte color;
+
+ _key = _oldKey = -1;
+ _keyboardInput = false;
+ int found = saves.getHighlightedButton();
+
+ if (events._pressed || events._released) {
+ events.clearKeyboard();
+
+ // Check for a filename entry being highlighted
+ if ((events._pressed || events._released) && mousePos.y > (CONTROLS_Y + 10)) {
+ int found1 = 0;
+ for (_selector = 0; (_selector < ONSCREEN_FILES_COUNT) && !found1; ++_selector)
+ if (mousePos.y > (CONTROLS_Y + 11 + _selector * 10) && mousePos.y < (CONTROLS_Y + 21 + _selector * 10))
+ found1 = 1;
+
+ if (_selector + saves._savegameIndex - 1 < MAX_SAVEGAME_SLOTS + (saves._envMode != SAVEMODE_LOAD))
+ _selector = _selector + saves._savegameIndex - 1;
+ else
+ _selector = -1;
+
+ if (!found1)
+ _selector = -1;
+ }
+
+ // Handle selecting buttons, if any
+ saves.highlightButtons(found);
+
+ if (found == 0 || found == 5)
+ saves._envMode = SAVEMODE_NONE;
+ }
+
+ if (_keyPress) {
+ _key = toupper(_keyPress);
+
+ // Escape _key will close the dialog
+ if (_key == Common::KEYCODE_ESCAPE)
+ _key = 'E';
+
+ if (_key == 'E' || _key == 'L' || _key == 'S' || _key == 'U' || _key == 'D' || _key == 'Q') {
+ const char *chP = strchr(ENV_COMMANDS, _key);
+ int btnIndex = !chP ? -1 : chP - ENV_COMMANDS;
+ saves.highlightButtons(btnIndex);
+ _keyboardInput = true;
+
+ if (_key == 'E' || _key == 'Q') {
+ saves._envMode = SAVEMODE_NONE;
+ } else if (_key >= '1' && _key <= '9') {
+ _keyboardInput = true;
+ _selector = _key - '1';
+ if (_selector >= MAX_SAVEGAME_SLOTS + (saves._envMode == SAVEMODE_LOAD ? 0 : 1))
+ _selector = -1;
+
+ if (saves.checkGameOnScreen(_selector))
+ _oldSelector = _selector;
+ } else {
+ _selector = -1;
+ }
+ }
+ }
+
+ if (_selector != _oldSelector) {
+ if (_oldSelector != -1 && _oldSelector >= saves._savegameIndex && _oldSelector < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) {
+ screen.print(Common::Point(6, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", _oldSelector + 1);
+ screen.print(Common::Point(24, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10),
+ INV_FOREGROUND, "%s", saves._savegames[_oldSelector].c_str());
+ }
+
+ if (_selector != -1) {
+ screen.print(Common::Point(6, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10),
+ TALK_FOREGROUND, "%d.", _selector + 1);
+ screen.print(Common::Point(24, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10),
+ TALK_FOREGROUND, "%s", saves._savegames[_selector].c_str());
+ }
+
+ _oldSelector = _selector;
+ }
+
+ if (events._released || _keyboardInput) {
+ if ((found == 0 && events._released) || _key == 'E') {
+ banishWindow();
+ _windowBounds.top = CONTROLS_Y1;
+
+ events._pressed = events._released = _keyboardInput = false;
+ _keyPress = '\0';
+ } else if ((found == 1 && events._released) || _key == 'L') {
+ saves._envMode = SAVEMODE_LOAD;
+ if (_selector != -1) {
+ saves.loadGame(_selector + 1);
+ }
+ } else if ((found == 2 && events._released) || _key == 'S') {
+ saves._envMode = SAVEMODE_SAVE;
+ if (_selector != -1) {
+ if (saves.checkGameOnScreen(_selector))
+ _oldSelector = _selector;
+
+ if (saves.promptForDescription(_selector)) {
+ saves.saveGame(_selector + 1, saves._savegames[_selector]);
+
+ banishWindow(1);
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = -1;
+ _keyPress = '\0';
+ _keyboardInput = false;
+ } else {
+ if (!talk._talkToAbort) {
+ screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10,
+ SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND);
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND,
+ "%d.", _selector + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND,
+ "%s", saves._savegames[_selector].c_str());
+
+ screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10);
+ _selector = _oldSelector = -1;
+ }
+ }
+ }
+ } else if (((found == 3 && events._released) || _key == 'U') && saves._savegameIndex) {
+ bool moreKeys;
+ do {
+ saves._savegameIndex--;
+ screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+
+ for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) {
+ color = INV_FOREGROUND;
+ if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT))
+ color = TALK_FOREGROUND;
+
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%s", saves._savegames[idx].c_str());
+ }
+
+ screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT));
+
+ color = !saves._savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up");
+ color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down");
+
+ // Check whether there are more pending U keys pressed
+ moreKeys = false;
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ _key = toupper(keyState.keycode);
+ moreKeys = _key == 'U';
+ }
+ } while ((saves._savegameIndex) && moreKeys);
+ } else if (((found == 4 && events._released) || _key == 'D') && saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)) {
+ bool moreKeys;
+ do {
+ saves._savegameIndex++;
+ screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+
+ for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) {
+ if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT))
+ color = TALK_FOREGROUND;
+ else
+ color = INV_FOREGROUND;
+
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color,
+ "%d.", idx + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color,
+ "%s", saves._savegames[idx].c_str());
+ }
+
+ screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT));
+
+ color = (!saves._savegameIndex) ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up");
+
+ color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down");
+
+ // Check whether there are more pending D keys pressed
+ moreKeys = false;
+ if (events.kbHit()) {
+ Common::KeyState keyState;
+ _key = toupper(keyState.keycode);
+
+ moreKeys = _key == 'D';
+ }
+ } while (saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) && moreKeys);
+ } else if ((found == 5 && events._released) || _key == 'Q') {
+ clearWindow();
+ screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, "Are you sure you wish to Quit ?");
+ screen.vgaBar(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR);
+
+ screen.makeButton(Common::Rect(112, CONTROLS_Y, 160, CONTROLS_Y + 10), 136 - screen.stringWidth("Yes") / 2, "Yes");
+ screen.makeButton(Common::Rect(161, CONTROLS_Y, 209, CONTROLS_Y + 10), 184 - screen.stringWidth("No") / 2, "No");
+ screen.slamArea(112, CONTROLS_Y, 97, 10);
+
+ do {
+ scene.doBgAnim();
+
+ if (talk._talkToAbort)
+ return;
+
+ events.pollEventsAndWait();
+ events.setButtonState();
+ mousePos = events.mousePos();
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ _key = toupper(keyState.keycode);
+
+ if (_key == 'X' && (keyState.flags & Common::KBD_ALT) != 0) {
+ _vm->quitGame();
+ events.pollEvents();
+ return;
+ }
+
+ if (_key == Common::KEYCODE_ESCAPE)
+ _key = 'N';
+
+ if (_key == Common::KEYCODE_RETURN || _key == ' ') {
+ events._pressed = false;
+ events._released = true;
+ events._oldButtons = 0;
+ _keyPress = '\0';
+ }
+ }
+
+ if (events._pressed || events._released) {
+ if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9))
+ color = COMMAND_HIGHLIGHTED;
+ else
+ color = COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(136, CONTROLS_Y), color, true, "Yes");
+
+ if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9))
+ color = COMMAND_HIGHLIGHTED;
+ else
+ color = COMMAND_FOREGROUND;
+ screen.buttonPrint(Common::Point(184, CONTROLS_Y), color, true, "No");
+ }
+
+ if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released)
+ _key = 'Y';
+
+ if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released)
+ _key = 'N';
+ } while (!_vm->shouldQuit() && _key != 'Y' && _key != 'N');
+
+ if (_key == 'Y') {
+ _vm->quitGame();
+ events.pollEvents();
+ return;
+ } else {
+ screen.buttonPrint(Common::Point(184, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "No");
+ banishWindow(1);
+ _windowBounds.top = CONTROLS_Y1;
+ _key = -1;
+ }
+ } else {
+ if (_selector != -1) {
+ // Are we already in Load mode?
+ if (saves._envMode == SAVEMODE_LOAD) {
+ saves.loadGame(_selector + 1);
+ } else if (saves._envMode == SAVEMODE_SAVE || saves.isSlotEmpty(_selector)) {
+ // We're already in save mode, or pointing to an empty save slot
+ if (saves.checkGameOnScreen(_selector))
+ _oldSelector = _selector;
+
+ if (saves.promptForDescription(_selector)) {
+ saves.saveGame(_selector + 1, saves._savegames[_selector]);
+ banishWindow();
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = -1;
+ _keyPress = '\0';
+ _keyboardInput = false;
+ } else {
+ if (!talk._talkToAbort) {
+ screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10,
+ 317, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND);
+ screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10),
+ INV_FOREGROUND, "%d.", _selector + 1);
+ screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10),
+ INV_FOREGROUND, "%s", saves._savegames[_selector].c_str());
+ screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10);
+ _selector = _oldSelector = -1;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void ScalpelUserInterface::doInvControl() {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ Scene &scene = *_vm->_scene;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int colors[8];
+ Common::Point mousePos = events.mousePos();
+
+ _key = _oldKey = -1;
+ _keyboardInput = false;
+
+ // Check whether any inventory slot is highlighted
+ int found = -1;
+ Common::fill(&colors[0], &colors[8], (int)COMMAND_FOREGROUND);
+ for (int idx = 0; idx < 8; ++idx) {
+ Common::Rect r(INVENTORY_POINTS[idx][0], CONTROLS_Y1,
+ INVENTORY_POINTS[idx][1], CONTROLS_Y1 + 10);
+ if (r.contains(mousePos)) {
+ found = idx;
+ break;
+ }
+ }
+
+ if (events._pressed || events._released) {
+ events.clearKeyboard();
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit);
+ Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look);
+ Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use);
+ Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give);
+
+ if (found != -1)
+ // If a slot highlighted, set its color
+ colors[found] = COMMAND_HIGHLIGHTED;
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, fixedText_Exit);
+
+ if (found >= 0 && found <= 3) {
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, fixedText_Look);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, fixedText_Use);
+ screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, fixedText_Give);
+ inv._invMode = (InvMode)found;
+ _selector = -1;
+ }
+
+ if (inv._invIndex) {
+ screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), colors[4], "^^");
+ screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), colors[5], "^");
+ }
+
+ if ((inv._holdings - inv._invIndex) > 6) {
+ screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), colors[6], "_");
+ screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), colors[7], "__");
+ }
+
+ bool flag = false;
+ if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) {
+ Common::Rect r(15, CONTROLS_Y1 + 11, 314, SHERLOCK_SCREEN_HEIGHT - 2);
+ if (r.contains(mousePos)) {
+ _selector = (mousePos.x - 6) / 52 + inv._invIndex;
+ if (_selector < inv._holdings)
+ flag = true;
+ }
+ }
+
+ if (!flag && mousePos.y >(CONTROLS_Y1 + 11))
+ _selector = -1;
+ }
+
+ if (_keyPress) {
+ _key = toupper(_keyPress);
+
+ if (_key == Common::KEYCODE_ESCAPE)
+ // Escape will also 'E'xit out of inventory display
+ _key = 'E';
+
+ if (_key == 'E' || _key == 'L' || _key == 'U' || _key == 'G'
+ || _key == '-' || _key == '+') {
+ InvMode temp = inv._invMode;
+
+ const char *chP = strchr(INVENTORY_COMMANDS, _key);
+ inv._invMode = !chP ? INVMODE_INVALID : (InvMode)(chP - INVENTORY_COMMANDS);
+ inv.invCommands(true);
+
+ inv._invMode = temp;
+ _keyboardInput = true;
+ if (_key == 'E')
+ inv._invMode = INVMODE_EXIT;
+ _selector = -1;
+ } else {
+ _selector = -1;
+ }
+ }
+
+ if (_selector != _oldSelector) {
+ if (_oldSelector != -1) {
+ // Un-highlight
+ if (_oldSelector >= inv._invIndex && _oldSelector < (inv._invIndex + 6))
+ inv.highlight(_oldSelector, BUTTON_MIDDLE);
+ }
+
+ if (_selector != -1)
+ inv.highlight(_selector, BUTTON_BACKGROUND);
+
+ _oldSelector = _selector;
+ }
+
+ if (events._released || _keyboardInput) {
+ if ((found == 0 && events._released) || _key == 'E') {
+ inv.freeInv();
+ _infoFlag = true;
+ clearInfo();
+ banishWindow(false);
+ _key = -1;
+ events.clearEvents();
+ events.setCursor(ARROW);
+ } else if ((found == 1 && events._released) || (_key == 'L')) {
+ inv._invMode = INVMODE_LOOK;
+ } else if ((found == 2 && events._released) || (_key == 'U')) {
+ inv._invMode = INVMODE_USE;
+ } else if ((found == 3 && events._released) || (_key == 'G')) {
+ inv._invMode = INVMODE_GIVE;
+ } else if (((found == 4 && events._released) || _key == ',') && inv._invIndex) {
+ if (inv._invIndex >= 6)
+ inv._invIndex -= 6;
+ else
+ inv._invIndex = 0;
+
+ screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1),
+ COMMAND_HIGHLIGHTED, "^^");
+ inv.freeGraphics();
+ inv.loadGraphics();
+ inv.putInv(SLAM_DISPLAY);
+ inv.invCommands(true);
+ } else if (((found == 5 && events._released) || _key == '-') && inv._invIndex > 0) {
+ --inv._invIndex;
+ screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "^");
+ inv.freeGraphics();
+ inv.loadGraphics();
+ inv.putInv(SLAM_DISPLAY);
+ inv.invCommands(true);
+ } else if (((found == 6 && events._released) || _key == '+') && (inv._holdings - inv._invIndex) > 6) {
+ ++inv._invIndex;
+ screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_");
+ inv.freeGraphics();
+ inv.loadGraphics();
+ inv.putInv(SLAM_DISPLAY);
+ inv.invCommands(true);
+ } else if (((found == 7 && events._released) || _key == '.') && (inv._holdings - inv._invIndex) > 6) {
+ inv._invIndex += 6;
+ if ((inv._holdings - 6) < inv._invIndex)
+ inv._invIndex = inv._holdings - 6;
+
+ screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_");
+ inv.freeGraphics();
+ inv.loadGraphics();
+ inv.putInv(SLAM_DISPLAY);
+ inv.invCommands(true);
+ } else {
+ // If something is being given, make sure it's being given to a person
+ if (inv._invMode == INVMODE_GIVE) {
+ if (_bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON)
+ _find = _bgFound;
+ else
+ _find = -1;
+ } else {
+ _find = _bgFound;
+ }
+
+ if ((mousePos.y < CONTROLS_Y1) && (inv._invMode == INVMODE_LOOK) && (_find >= 0) && (_find < 1000)) {
+ if (!scene._bgShapes[_find]._examine.empty() &&
+ scene._bgShapes[_find]._examine[0] >= ' ')
+ inv.refreshInv();
+ } else if (_selector != -1 || _find >= 0) {
+ // Selector is the inventory object that was clicked on, or selected.
+ // If it's -1, then no inventory item is highlighted yet. Otherwise,
+ // an object in the scene has been clicked.
+
+ if (_selector != -1 && inv._invMode == INVMODE_LOOK
+ && mousePos.y >(CONTROLS_Y1 + 11))
+ inv.refreshInv();
+
+ if (talk._talkToAbort)
+ return;
+
+ // Now check for the Use and Give actions. If inv_mode is INVMODE_GIVE,
+ // that means GIVE is in effect, _selector is the object being
+ // given, and _find is the target.
+ // The same applies to USE, except if _selector is -1, then USE
+ // is being tried on an object in the scene without an inventory
+ // object being highlighted first.
+
+ if ((inv._invMode == INVMODE_USE || (_selector != -1 && inv._invMode == INVMODE_GIVE)) && _find >= 0) {
+ events._pressed = events._released = false;
+ _infoFlag = true;
+ clearInfo();
+
+ int tempSel = _selector; // Save the selector
+ _selector = -1;
+
+ inv.putInv(SLAM_DISPLAY);
+ _selector = tempSel; // Restore it
+ InvMode tempMode = inv._invMode;
+ inv._invMode = INVMODE_USE55;
+ inv.invCommands(true);
+
+ _infoFlag = true;
+ clearInfo();
+ banishWindow(false);
+ _key = -1;
+
+ inv.freeInv();
+
+ bool giveFl = (tempMode >= INVMODE_GIVE);
+ if (_selector >= 0)
+ // Use/Give inv object with scene object
+ checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, kFixedTextAction_Use, _find, giveFl);
+ else
+ // Now inv object has been highlighted
+ checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", kFixedTextAction_Use, _find, giveFl);
+
+ _selector = _oldSelector = -1;
+ }
+ }
+ }
+ }
+}
+
+void ScalpelUserInterface::doLookControl() {
+ Events &events = *_vm->_events;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ Screen &screen = *_vm->_screen;
+
+ _key = _oldKey = -1;
+ _keyboardInput = (_keyPress != '\0');
+
+ if (events._released || events._rightReleased || _keyboardInput) {
+ // Is an inventory object being looked at?
+ if (!_invLookFlag) {
+ // Is there any remaining text to display?
+ if (!_descStr.empty()) {
+ printObjectDesc(_descStr, false);
+ } else if (!_lookHelp) {
+ // Need to close the window and depress the Look button
+ Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
+ offsetButton3DO(pt, 0);
+ screen._backBuffer2.blitFrom((*_controls)[0], pt);
+ banishWindow(true);
+
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = COMMANDS[LOOK_MODE - 1];
+ _temp = _oldTemp = 0;
+ _menuMode = LOOK_MODE;
+ events.clearEvents();
+
+ // Restore UI
+ drawInterface();
+ } else {
+ events.setCursor(ARROW);
+ banishWindow(true);
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = -1;
+ _temp = _oldTemp = 0;
+ _menuMode = STD_MODE;
+ events.clearEvents();
+ }
+ } else {
+ // Looking at an inventory object
+ // Backup the user interface
+ Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1);
+ tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0),
+ Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ inv.drawInventory(INVENTORY_DONT_DISPLAY);
+ banishWindow(true);
+
+ // Restore the ui
+ screen._backBuffer2.blitFrom(tempSurface, Common::Point(0, CONTROLS_Y1));
+
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = COMMANDS[LOOK_MODE - 1];
+ _temp = _oldTemp = 0;
+ events.clearEvents();
+ _invLookFlag = false;
+ _menuMode = INV_MODE;
+ _windowOpen = true;
+ }
+ }
+}
+
+void ScalpelUserInterface::doMainControl() {
+ Events &events = *_vm->_events;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves;
+ Common::Point pt = events.mousePos();
+
+ if ((events._pressed || events._released) && pt.y > CONTROLS_Y) {
+ events.clearKeyboard();
+ _key = -1;
+
+ // Check whether the mouse is in any of the command areas
+ for (_temp = 0; (_temp < 12) && (_key == -1); ++_temp) {
+ Common::Rect r(MENU_POINTS[_temp][0], MENU_POINTS[_temp][1],
+ MENU_POINTS[_temp][2], MENU_POINTS[_temp][3]);
+ if (IS_3DO && _temp >= 0 && _temp <= 2) {
+ r.left += UI_OFFSET_3DO - 1;
+ r.right += UI_OFFSET_3DO - 1;
+ }
+ if (r.contains(pt))
+ _key = COMMANDS[_temp];
+ }
+ --_temp;
+ } else if (_keyPress) {
+ // Keyboard control
+ _keyboardInput = true;
+
+ if (_keyPress >= 'A' && _keyPress <= 'Z') {
+ const char *c = strchr(COMMANDS, _keyPress);
+ _temp = !c ? 12 : c - COMMANDS;
+ } else {
+ _temp = 12;
+ }
+
+ if (_temp == 12)
+ _key = -1;
+
+ if (events._rightPressed) {
+ _temp = 12;
+ _key = -1;
+ }
+ } else if (!events._released) {
+ _key = -1;
+ }
+
+ // Check if the button being pointed to has changed
+ if (_oldKey != _key && !_windowOpen) {
+ // Clear the info line
+ _infoFlag = true;
+ clearInfo();
+
+ // If there was an old button selected, restore it
+ if (_oldKey != -1) {
+ _menuMode = STD_MODE;
+ restoreButton(_oldTemp);
+ }
+
+ // If a new button is being pointed to, highlight it
+ if (_key != -1 && _temp < 12 && !_keyboardInput)
+ depressButton(_temp);
+
+ // Save the new button selection
+ _oldKey = _key;
+ _oldTemp = _temp;
+ }
+
+ if (!events._pressed && !_windowOpen) {
+ switch (_key) {
+ case 'L':
+ toggleButton(0);
+ break;
+ case 'M':
+ toggleButton(1);
+ break;
+ case 'T':
+ toggleButton(2);
+ break;
+ case 'P':
+ toggleButton(3);
+ break;
+ case 'O':
+ toggleButton(4);
+ break;
+ case 'C':
+ toggleButton(5);
+ break;
+ case 'I':
+ pushButton(6);
+ _selector = _oldSelector = -1;
+ _menuMode = INV_MODE;
+ inv.drawInventory(PLAIN_INVENTORY);
+ break;
+ case 'U':
+ pushButton(7);
+ _selector = _oldSelector = -1;
+ _menuMode = USE_MODE;
+ inv.drawInventory(USE_INVENTORY_MODE);
+ break;
+ case 'G':
+ pushButton(8);
+ _selector = _oldSelector = -1;
+ _menuMode = GIVE_MODE;
+ inv.drawInventory(GIVE_INVENTORY_MODE);
+ break;
+ case 'J':
+ pushButton(9);
+ _menuMode = JOURNAL_MODE;
+ journalControl();
+ break;
+ case 'F':
+ pushButton(10);
+
+ // Create a thumbnail of the current screen before the files dialog is shown, in case
+ // the user saves the game
+ saves.createThumbnail();
+
+ _selector = _oldSelector = -1;
+
+ if (_vm->_showOriginalSavesDialog) {
+ // Show the original dialog
+ _menuMode = FILES_MODE;
+ saves.drawInterface();
+ _windowOpen = true;
+ } else {
+ // Show the ScummVM GMM instead
+ _vm->_canLoadSave = true;
+ _vm->openMainMenuDialog();
+ _vm->_canLoadSave = false;
+ }
+ break;
+ case 'S':
+ pushButton(11);
+ _menuMode = SETUP_MODE;
+ Settings::show(_vm);
+ break;
+ default:
+ break;
+ }
+
+ _help = _oldHelp = _oldBgFound = -1;
+ }
+}
+
+void ScalpelUserInterface::doMiscControl(int allowed) {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ if (events._released) {
+ _temp = _bgFound;
+ if (_bgFound != -1) {
+ // Only allow pointing to objects, not people
+ if (_bgFound < 1000) {
+ events.clearEvents();
+ Object &obj = scene._bgShapes[_bgFound];
+
+ switch (allowed) {
+ case ALLOW_OPEN:
+ checkAction(obj._aOpen, _temp, kFixedTextAction_Open);
+ if (_menuMode != TALK_MODE && !talk._talkToAbort) {
+ _menuMode = STD_MODE;
+ restoreButton(OPEN_MODE - 1);
+ _key = _oldKey = -1;
+ }
+ break;
+
+ case ALLOW_CLOSE:
+ checkAction(obj._aClose, _temp, kFixedTextAction_Close);
+ if (_menuMode != TALK_MODE && !talk._talkToAbort) {
+ _menuMode = STD_MODE;
+ restoreButton(CLOSE_MODE - 1);
+ _key = _oldKey = -1;
+ }
+ break;
+
+ case ALLOW_MOVE:
+ checkAction(obj._aMove, _temp, kFixedTextAction_Move);
+ if (_menuMode != TALK_MODE && !talk._talkToAbort) {
+ _menuMode = STD_MODE;
+ restoreButton(MOVE_MODE - 1);
+ _key = _oldKey = -1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+void ScalpelUserInterface::doPickControl() {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ if (events._released) {
+ if ((_temp = _bgFound) != -1) {
+ events.clearEvents();
+
+ // Don't allow characters to be picked up
+ if (_bgFound < 1000) {
+ scene._bgShapes[_bgFound].pickUpObject(kFixedTextAction_Pick);
+
+ if (!talk._talkToAbort && _menuMode != TALK_MODE) {
+ _key = _oldKey = -1;
+ _menuMode = STD_MODE;
+ restoreButton(PICKUP_MODE - 1);
+ }
+ }
+ }
+ }
+}
+
+void ScalpelUserInterface::doTalkControl() {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal;
+ ScalpelPeople &people = *(ScalpelPeople *)_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Sound &sound = *_vm->_sound;
+ Talk &talk = *_vm->_talk;
+ Common::Point mousePos = events.mousePos();
+
+ _key = _oldKey = -1;
+ _keyboardInput = false;
+
+ Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit);
+ Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up);
+ Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down);
+
+ if (events._pressed || events._released) {
+ events.clearKeyboard();
+
+ // Handle button printing
+ if (mousePos.x > 99 && mousePos.x < 138 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && !_endKeyActive)
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Exit);
+ else if (_endKeyActive)
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit);
+
+ if (mousePos.x > 140 && mousePos.x < 170 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkUp)
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up);
+ else if (talk._moreTalkUp)
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up);
+
+ if (mousePos.x > 181&& mousePos.x < 220 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkDown)
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down);
+ else if (talk._moreTalkDown)
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down);
+
+ bool found = false;
+ for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) {
+ if (mousePos.y > talk._statements[_selector]._talkPos.top &&
+ mousePos.y < talk._statements[_selector]._talkPos.bottom)
+ found = true;
+ }
+ --_selector;
+ if (!found)
+ _selector = -1;
+ }
+
+ if (_keyPress) {
+ _key = toupper(_keyPress);
+ if (_key == Common::KEYCODE_ESCAPE)
+ _key = 'E';
+
+ // Check for number press indicating reply line
+ if (_key >= '1' && _key <= ('1' + (int)talk._statements.size() - 1)) {
+ for (uint idx = 0; idx < talk._statements.size(); ++idx) {
+ if (talk._statements[idx]._talkMap == (_key - '1')) {
+ // Found the given statement
+ _selector = idx;
+ _key = -1;
+ _keyboardInput = true;
+ break;
+ }
+ }
+ } else if (_key == 'E' || _key == 'U' || _key == 'D') {
+ _keyboardInput = true;
+ } else {
+ _selector = -1;
+ }
+ }
+
+ if (_selector != _oldSelector) {
+ // Remove highlighting from previous line, if any
+ if (_oldSelector != -1) {
+ if (!((talk._talkHistory[talk._converseNum][_oldSelector] >> (_oldSelector & 7)) & 1))
+ talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, INV_FOREGROUND,
+ talk._statements[_oldSelector]._talkPos.top, true);
+ else
+ talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, TALK_NULL,
+ talk._statements[_oldSelector]._talkPos.top, true);
+ }
+
+ // Add highlighting to new line, if any
+ if (_selector != -1)
+ talk.talkLine(_selector, talk._statements[_selector]._talkMap, TALK_FOREGROUND,
+ talk._statements[_selector]._talkPos.top, true);
+
+ _oldSelector = _selector;
+ }
+
+ if (events._released || _keyboardInput) {
+ if (((Common::Rect(99, CONTROLS_Y, 138, CONTROLS_Y + 10).contains(mousePos) && events._released)
+ || _key == 'E') && _endKeyActive) {
+ talk.freeTalkVars();
+ talk.pullSequence();
+
+ drawInterface(2);
+ banishWindow();
+ _windowBounds.top = CONTROLS_Y1;
+ } else if (((Common::Rect(140, CONTROLS_Y, 179, CONTROLS_Y + 10).contains(mousePos) && events._released)
+ || _key == 'U') && talk._moreTalkUp) {
+ while (talk._statements[--talk._talkIndex]._talkMap == -1)
+ ;
+ screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+ talk.displayTalk(false);
+
+ screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2));
+ } else if (((Common::Rect(181, CONTROLS_Y, 220, CONTROLS_Y + 10).contains(mousePos) && events._released)
+ || _key == 'D') && talk._moreTalkDown) {
+ do {
+ ++talk._talkIndex;
+ } while (talk._talkIndex < (int)talk._statements.size() && talk._statements[talk._talkIndex]._talkMap == -1);
+
+ screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND);
+ talk.displayTalk(false);
+
+ screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2));
+ } else if (_selector != -1) {
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit);
+ screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up);
+ screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down);
+
+ // If the reply is new, add it to the journal
+ if (!talk._talkHistory[talk._converseNum][_selector]) {
+ journal.record(talk._converseNum, _selector);
+
+ // Add any Holmes point to Holmes' total, if any
+ if (talk._statements[_selector]._quotient)
+ people._holmesQuotient += talk._statements[_selector]._quotient;
+ }
+
+ // Flag the response as having been used
+ talk._talkHistory[talk._converseNum][_selector] = true;
+
+ clearWindow();
+ screen.print(Common::Point(16, CONTROLS_Y + 12), TALK_FOREGROUND, "Sherlock Holmes");
+ talk.talkLine(_selector + 128, talk._statements[_selector]._talkMap, COMMAND_FOREGROUND, CONTROLS_Y + 21, true);
+
+ switch (talk._statements[_selector]._portraitSide & 3) {
+ case 0:
+ case 1:
+ people._portraitSide = 20;
+ break;
+ case 2:
+ people._portraitSide = 220;
+ break;
+ case 3:
+ people._portraitSide = 120;
+ break;
+ }
+
+ // Check for flipping Holmes
+ if (talk._statements[_selector]._portraitSide & REVERSE_DIRECTION)
+ people._holmesFlip = true;
+
+ talk._speaker = 0;
+ people.setTalking(0);
+
+ if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) {
+ sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY);
+
+ // Set voices as an indicator for waiting
+ sound._voices = 2;
+ sound._speechOn = *sound._soundIsOn;
+ } else {
+ sound._speechOn = false;
+ }
+
+ // Trigger to play 3DO movie
+ talk.talk3DOMovieTrigger(0);
+
+ talk.waitForMore(talk._statements[_selector]._statement.size());
+ if (talk._talkToAbort)
+ return;
+
+ people.clearTalking();
+ if (talk._talkToAbort)
+ return;
+
+ while (!_vm->shouldQuit()) {
+ talk._scriptSelect = _selector;
+ talk._speaker = talk._talkTo;
+ talk.doScript(talk._statements[_selector]._reply);
+
+ if (!talk._talkToAbort) {
+ if (!talk._talkStealth)
+ clearWindow();
+
+ if (!talk._statements[_selector]._modified.empty()) {
+ for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx) {
+ _vm->setFlags(talk._statements[_selector]._modified[idx]);
+ }
+
+ talk.setTalkMap();
+ }
+
+ // Check for another linked talk file
+ Common::String linkFilename = talk._statements[_selector]._linkFile;
+ if (!linkFilename.empty() && !talk._scriptMoreFlag) {
+ talk.freeTalkVars();
+ talk.loadTalkFile(linkFilename);
+
+ // Find the first new statement
+ int select = _selector = _oldSelector = -1;
+ for (uint idx = 0; idx < talk._statements.size() && select == -1; ++idx) {
+ if (!talk._statements[idx]._talkMap)
+ select = talk._talkIndex = idx;
+ }
+
+ // See if the new statement is a stealth reply
+ talk._talkStealth = talk._statements[select]._statement.hasPrefix("^") ? 2 : 0;
+
+ // Is the new talk file a standard file, reply first file, or a stealth file
+ if (!talk._statements[select]._statement.hasPrefix("*") &&
+ !talk._statements[select]._statement.hasPrefix("^")) {
+ // Not a reply first file, so display the new selections
+ if (_endKeyActive)
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit);
+ else
+ screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit);
+
+ talk.displayTalk(true);
+ events.setCursor(ARROW);
+ break;
+ } else {
+ _selector = select;
+
+ if (!talk._talkHistory[talk._converseNum][_selector])
+ journal.record(talk._converseNum, _selector);
+
+ talk._talkHistory[talk._converseNum][_selector] = true;
+ }
+ } else {
+ talk.freeTalkVars();
+ talk.pullSequence();
+ banishWindow();
+ _windowBounds.top = CONTROLS_Y1;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ events._pressed = events._released = false;
+ events._oldButtons = 0;
+ talk._talkStealth = 0;
+
+ // If a script was pushed onto the script stack, restore it
+ if (!talk._scriptStack.empty()) {
+ ScriptStackEntry stackEntry = talk._scriptStack.pop();
+ talk._scriptName = stackEntry._name;
+ talk._scriptSaveIndex = stackEntry._currentIndex;
+ talk._scriptSelect = stackEntry._select;
+ }
+ }
+ }
+}
+
+void ScalpelUserInterface::journalControl() {
+ Events &events = *_vm->_events;
+ ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ bool doneFlag = false;
+
+ // Draw the journal screen
+ journal.drawInterface();
+
+ // Handle journal events
+ do {
+ _key = -1;
+ events.setButtonState();
+
+ // Handle keypresses
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ if (keyState.keycode == Common::KEYCODE_x && (keyState.flags & Common::KBD_ALT)) {
+ _vm->quitGame();
+ return;
+ } else if (keyState.keycode == Common::KEYCODE_e || keyState.keycode == Common::KEYCODE_ESCAPE) {
+ doneFlag = true;
+ } else {
+ _key = toupper(keyState.keycode);
+ }
+ }
+
+ if (!doneFlag)
+ doneFlag = journal.handleEvents(_key);
+ } while (!_vm->shouldQuit() && !doneFlag);
+
+ // Finish up
+ _infoFlag = _keyboardInput = false;
+ _keyPress = '\0';
+ _windowOpen = false;
+ _windowBounds.top = CONTROLS_Y1;
+ _key = -1;
+ _menuMode = STD_MODE;
+
+ // Reset the palette
+ screen.setPalette(screen._cMap);
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2);
+ scene.updateBackground();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+}
+
+void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool firstTime) {
+ Events &events = *_vm->_events;
+ ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Talk &talk = *_vm->_talk;
+
+ if (str.hasPrefix("_")) {
+ _lookScriptFlag = true;
+ events.setCursor(MAGNIFY);
+ int savedSelector = _selector;
+ talk.talkTo(str.c_str() + 1);
+ _lookScriptFlag = false;
+
+ if (talk._talkToAbort) {
+ events.setCursor(ARROW);
+ return;
+ }
+
+ // Check if looking at an inventory object
+ if (!_invLookFlag) {
+ // See if this look was called by a right button click or not
+ if (!_lookHelp) {
+ // If it wasn't a right button click, then we need depress
+ // the look button before we close the window. So save a copy of the
+ // menu area, and draw the controls onto it
+ Surface tempSurface((*_controls)[0]._frame.w, (*_controls)[0]._frame.h);
+ Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]);
+ offsetButton3DO(pt, 0);
+
+ tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0),
+ Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h()));
+ screen._backBuffer2.transBlitFrom((*_controls)[0], pt);
+
+ banishWindow(1);
+ events.setCursor(MAGNIFY);
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = COMMANDS[LOOK_MODE - 1];
+ _temp = _oldTemp = 0;
+ _menuMode = LOOK_MODE;
+ events.clearEvents();
+
+ screen._backBuffer2.blitFrom(tempSurface, pt);
+ } else {
+ events.setCursor(ARROW);
+ banishWindow(true);
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = -1;
+ _temp = _oldTemp = 0;
+ _menuMode = STD_MODE;
+ _lookHelp = 0;
+ events.clearEvents();
+ }
+ } else {
+ // Looking at an inventory object
+ _selector = _oldSelector = savedSelector;
+
+ // Reload the inventory graphics and draw the inventory
+ inv.loadInv();
+ inv.putInv(SLAM_SECONDARY_BUFFER);
+ inv.freeInv();
+ banishWindow(1);
+
+ _windowBounds.top = CONTROLS_Y1;
+ _key = _oldKey = COMMANDS[INV_MODE - 1];
+ _temp = _oldTemp = 0;
+ events.clearEvents();
+
+ _invLookFlag = 0;
+ _menuMode = INV_MODE;
+ _windowOpen = true;
+ }
+
+ return;
+ }
+
+ Surface &bb = *screen._backBuffer;
+ if (firstTime) {
+ // Only draw the border on the first call
+ _infoFlag = true;
+ clearInfo();
+
+ bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH,
+ CONTROLS_Y1 + 10), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 1, SHERLOCK_SCREEN_HEIGHT - 1),
+ BORDER_COLOR);
+ bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ }
+
+ // Clear background
+ bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+
+ _windowBounds.top = CONTROLS_Y;
+ events.clearEvents();
+
+ // Loop through displaying up to five lines
+ bool endOfStr = false;
+ const char *msgP = str.c_str();
+ for (int lineNum = 0; lineNum < ONSCREEN_FILES_COUNT && !endOfStr; ++lineNum) {
+ int width = 0;
+ const char *lineStartP = msgP;
+
+ // Determine how much can be displayed on the line
+ do {
+ width += screen.charWidth(*msgP++);
+ } while (width < 300 && *msgP);
+
+ if (*msgP)
+ --msgP;
+ else
+ endOfStr = true;
+
+ // If the line needs to be wrapped, scan backwards to find
+ // the end of the previous word as a splitting point
+ if (width >= 300) {
+ while (*msgP != ' ')
+ --msgP;
+ endOfStr = false;
+ }
+
+ // Print out the line
+ Common::String line(lineStartP, msgP);
+ screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9),
+ INV_FOREGROUND, "%s", line.c_str());
+
+ if (!endOfStr)
+ // Start next line at start of the nxet word after space
+ ++msgP;
+ }
+
+ // Handle display depending on whether all the message was shown
+ if (!endOfStr) {
+ screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10),
+ (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2,
+ PRESS_KEY_FOR_MORE);
+ screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH -
+ screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, CONTROLS_Y),
+ COMMAND_FOREGROUND, "P");
+ _descStr = msgP;
+ } else {
+ screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10),
+ (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2,
+ PRESS_KEY_TO_CONTINUE);
+ screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH -
+ screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, CONTROLS_Y),
+ COMMAND_FOREGROUND, "P");
+ _descStr = "";
+ }
+
+ if (firstTime) {
+ if (!_slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ // Display the window
+ summonWindow();
+ }
+
+ _selector = _oldSelector = -1;
+ _windowOpen = true;
+ } else {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT));
+ }
+}
+
+void ScalpelUserInterface::printObjectDesc() {
+ printObjectDesc(_cAnimStr, true);
+}
+
+void ScalpelUserInterface::summonWindow(const Surface &bgSurface, bool slideUp) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+
+ if (_windowOpen)
+ // A window is already open, so can't open another one
+ return;
+
+ if (slideUp) {
+ // Gradually slide up the display of the window
+ for (int idx = 1; idx <= bgSurface.h(); idx += 2) {
+ screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx),
+ Common::Rect(0, 0, bgSurface.w(), idx));
+ screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - idx,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ events.delay(10);
+ }
+ } else {
+ // Gradually slide down the display of the window
+ for (int idx = 1; idx <= bgSurface.h(); idx += 2) {
+ screen._backBuffer->blitFrom(bgSurface,
+ Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()),
+ Common::Rect(0, bgSurface.h() - idx, bgSurface.w(), bgSurface.h()));
+ screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(),
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.h() + idx));
+
+ events.delay(10);
+ }
+ }
+
+ // Final display of the entire window
+ screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()),
+ Common::Rect(0, 0, bgSurface.w(), bgSurface.h()));
+ screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), bgSurface.w(), bgSurface.h());
+
+ _windowOpen = true;
+}
+
+void ScalpelUserInterface::summonWindow(bool slideUp, int height) {
+ Screen &screen = *_vm->_screen;
+
+ // Extract the window that's been drawn on the back buffer
+ Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - height);
+ Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r);
+
+ // Remove drawn window with original user interface
+ screen._backBuffer1.blitFrom(screen._backBuffer2,
+ Common::Point(0, height), r);
+
+ // Display the window gradually on-screen
+ summonWindow(tempSurface, slideUp);
+}
+
+void ScalpelUserInterface::banishWindow(bool slideUp) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+
+ if (_windowOpen) {
+ if (slideUp || !_slideWindows) {
+ // Slide window down
+ // Only slide the window if the window style allows it
+ if (_slideWindows) {
+ for (int idx = 2; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y); idx += 2) {
+ // Shift the window down by 2 lines
+ byte *pSrc = (byte *)screen._backBuffer1.getBasePtr(0, CONTROLS_Y + idx - 2);
+ byte *pSrcEnd = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - 2);
+ byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT);
+ Common::copy_backward(pSrc, pSrcEnd, pDest);
+
+ // Restore lines from the ui in the secondary back buffer
+ screen._backBuffer1.blitFrom(screen._backBuffer2,
+ Common::Point(0, CONTROLS_Y),
+ Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + idx));
+
+ screen.slamArea(0, CONTROLS_Y + idx - 2, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y - idx + 2);
+ events.delay(10);
+ }
+
+ // Restore final two old lines
+ screen._backBuffer1.blitFrom(screen._backBuffer2,
+ Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 2),
+ Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2,
+ SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, 2);
+ } else {
+ // Restore old area to completely erase window
+ screen._backBuffer1.blitFrom(screen._backBuffer2,
+ Common::Point(0, CONTROLS_Y),
+ Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT));
+ }
+ } else {
+ // Slide the original user interface up to cover the dialog
+ for (int idx = 1; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); idx += 2) {
+ byte *pSrc = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1);
+ byte *pSrcEnd = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1 + idx);
+ byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - idx);
+ Common::copy(pSrc, pSrcEnd, pDest);
+
+ screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - idx, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT);
+ events.delay(10);
+ }
+
+ // Show entire final area
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1),
+ Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ }
+
+ _infoFlag = false;
+ _windowOpen = false;
+ }
+
+ _menuMode = STD_MODE;
+}
+
+void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::String &invName,
+ FixedTextActionId fixedTextActionId, int objNum, bool giveMode) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ Inventory &inv = *_vm->_inventory;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ bool printed = fixedTextActionId == kFixedTextAction_Invalid;
+
+ if (objNum >= 1000) {
+ // Holmes was specified, so do nothing
+ _infoFlag = true;
+ clearInfo();
+ _infoFlag = true;
+
+ // Display error message
+ _menuCounter = 30;
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that to yourself.");
+ return;
+ }
+
+ // Scan for target item
+ int targetNum = -1;
+ if (giveMode) {
+ for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) {
+ if ((use[idx]._target.equalsIgnoreCase("*GIVE*") || use[idx]._target.equalsIgnoreCase("*GIVEP*"))
+ && use[idx]._names[0].equalsIgnoreCase(invName)) {
+ // Found a match
+ targetNum = idx;
+ if (use[idx]._target.equalsIgnoreCase("*GIVE*"))
+ inv.deleteItemFromInventory(invName);
+ }
+ }
+ } else {
+ for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) {
+ if (use[idx]._target.equalsIgnoreCase(invName))
+ targetNum = idx;
+ }
+ }
+
+ if (targetNum != -1) {
+ // Found a target, so do the action
+ const UseType &action = use[targetNum];
+
+ events.setCursor(WAIT);
+
+ if (action._useFlag)
+ _vm->setFlags(action._useFlag);
+
+ if (action._cAnimNum != 99) {
+ if (action._cAnimNum == 0)
+ scene.startCAnim(9, action._cAnimSpeed);
+ else
+ scene.startCAnim(action._cAnimNum - 1, action._cAnimSpeed);
+ }
+
+ if (!talk._talkToAbort) {
+ Object &obj = scene._bgShapes[objNum];
+ for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
+ if (obj.checkNameForCodes(action._names[idx], fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+
+ // Print "Done..." as an ending, unless flagged for leaving scene or otherwise flagged
+ if (scene._goToScene != 1 && !printed && !talk._talkToAbort) {
+ _infoFlag = true;
+ clearInfo();
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done...");
+ _menuCounter = 25;
+ }
+ }
+ } else {
+ // Couldn't find target, so print error
+ _infoFlag = true;
+ clearInfo();
+
+ if (giveMode) {
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you.");
+ } else if (fixedTextActionId == kFixedTextAction_Invalid) {
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that.");
+ } else {
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0);
+ screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str());
+ }
+
+ _infoFlag = true;
+ _menuCounter = 30;
+ }
+
+ events.setCursor(ARROW);
+}
+
+void ScalpelUserInterface::offsetButton3DO(Common::Point &pt, int num) {
+ if (IS_3DO) {
+ if (num >= 0 && num <= 2)
+ pt.x += 15;
+ else if (num >= 6 && num <= 8)
+ pt.x -= 4;
+ else if (num >= 9 && num <= 11)
+ pt.x -= 8;
+ }
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h
new file mode 100644
index 0000000000..7829ffca9f
--- /dev/null
+++ b/engines/sherlock/scalpel/scalpel_user_interface.h
@@ -0,0 +1,223 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_UI_H
+#define SHERLOCK_SCALPEL_UI_H
+
+#include "common/scummsys.h"
+#include "sherlock/user_interface.h"
+
+namespace Sherlock {
+
+class Inventory;
+class Talk;
+
+namespace Scalpel {
+
+extern const char COMMANDS[13];
+extern const int MENU_POINTS[12][4];
+
+extern const int INVENTORY_POINTS[8][3];
+extern const char INVENTORY_COMMANDS[9];
+extern const char *const PRESS_KEY_FOR_MORE;
+extern const char *const PRESS_KEY_TO_CONTINUE;
+
+class Settings;
+
+class ScalpelUserInterface: public UserInterface {
+ friend class Settings;
+ friend class Talk;
+private:
+ char _keyPress;
+ int _lookHelp;
+ int _help, _oldHelp;
+ int _key, _oldKey;
+ int _temp, _oldTemp;
+ int _oldLook;
+ bool _keyboardInput;
+ bool _pause;
+ int _cNum;
+ Common::String _cAnimStr;
+ Common::String _descStr;
+ int _find;
+private:
+ /**
+ * Draws the image for a user interface button in the down/pressed state.
+ */
+ void depressButton(int num);
+
+ /**
+ * If he mouse button is pressed, then calls depressButton to draw the button
+ * as pressed; if not, it will show it as released with a call to "restoreButton".
+ */
+ void pushButton(int num);
+
+ /**
+ * By the time this method has been called, the graphics for the button change
+ * have already been drawn. This simply takes care of switching the mode around
+ * accordingly
+ */
+ void toggleButton(int num);
+
+ /**
+ * Print the name of an object in the scene
+ */
+ void lookScreen(const Common::Point &pt);
+
+ /**
+ * Gets the item in the inventory the mouse is on and display's it's description
+ */
+ void lookInv();
+
+ /**
+ * Handles input when the file list window is being displayed
+ */
+ void doEnvControl();
+
+ /**
+ * Handle input whilst the inventory is active
+ */
+ void doInvControl();
+
+ /**
+ * Handles waiting whilst an object's description window is open.
+ */
+ void doLookControl();
+
+ /**
+ * Handles input until one of the user interface buttons/commands is selected
+ */
+ void doMainControl();
+
+ /**
+ * Handles the input for the MOVE, OPEN, and CLOSE commands
+ */
+ void doMiscControl(int allowed);
+
+ /**
+ * Handles input for picking up items
+ */
+ void doPickControl();
+
+ /**
+ * Handles input when in talk mode. It highlights the buttons and available statements,
+ * and handles allowing the user to click on them
+ */
+ void doTalkControl();
+
+ /**
+ * Handles events when the Journal is active.
+ * @remarks Whilst this would in theory be better in the Journal class, since it displays in
+ * the user interface, it uses so many internal UI fields, that it sort of made some sense
+ * to put it in the UserInterface class.
+ */
+ void journalControl();
+
+ /**
+ * Checks to see whether a USE action is valid on the given object
+ */
+ void checkUseAction(const UseType *use, const Common::String &invName, FixedTextActionId fixedTextActionId,
+ int objNum, bool giveMode);
+
+ /**
+ * Print the previously selected object's decription
+ */
+ void printObjectDesc(const Common::String &str, bool firstTime);
+public:
+ ImageFile *_controlPanel;
+ ImageFile *_controls;
+ int _oldUse;
+public:
+ ScalpelUserInterface(SherlockEngine *vm);
+ virtual ~ScalpelUserInterface();
+
+ /**
+ * Handles counting down whilst checking for input, then clears the info line.
+ */
+ void whileMenuCounter();
+
+ /**
+ * Draws the image for the given user interface button in the up
+ * (not selected) position
+ */
+ void restoreButton(int num);
+
+ /**
+ * Creates a text window and uses it to display the in-depth description
+ * of the highlighted object
+ */
+ void examine();
+
+ void offsetButton3DO(Common::Point &pt, int num);
+public:
+ /**
+ * Resets the user interface
+ */
+ virtual void reset();
+
+ /**
+ * Main input handler for the user interface
+ */
+ virtual void handleInput();
+
+ /**
+ * Draw the user interface onto the screen's back buffers
+ */
+ virtual void drawInterface(int bufferNum = 3);
+
+ /**
+ * Displays a passed window by gradually scrolling it vertically on-screen
+ */
+ virtual void summonWindow(const Surface &bgSurface, bool slideUp = true);
+
+ /**
+ * Slide the window stored in the back buffer onto the screen
+ */
+ virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y);
+
+ /**
+ * Close a currently open window
+ * @param flag 0 = slide old window down, 1 = slide prior UI back up
+ */
+ virtual void banishWindow(bool slideUp = true);
+
+ /**
+ * Clears the info line of the screen
+ */
+ virtual void clearInfo();
+
+ /**
+ * Clear any active text window
+ */
+ virtual void clearWindow();
+
+ /**
+ * Print the previously selected object's decription
+ */
+ virtual void printObjectDesc();
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp
new file mode 100644
index 0000000000..f6769a4b99
--- /dev/null
+++ b/engines/sherlock/scalpel/settings.cpp
@@ -0,0 +1,343 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/settings.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+
+namespace Scalpel {
+
+static const int SETUP_POINTS[12][4] = {
+ { 4, 154, 101, 53 }, // Exit
+ { 4, 165, 101, 53 }, // Music Toggle
+ { 219, 165, 316, 268 }, // Voice Toggle
+ { 103, 165, 217, 160 }, // Sound Effects Toggle
+ { 219, 154, 316, 268 }, // Help Button Left/Right
+ { 103, 154, 217, 160 }, // New Font Style
+ { 4, 187, 101, 53 }, // Joystick Toggle
+ { 103, 187, 217, 160 }, // Calibrate Joystick
+ { 219, 176, 316, 268 }, // Fade Style
+ { 103, 176, 217, 160 }, // Window Open Style
+ { 4, 176, 101, 53 }, // Portraits Toggle
+ { 219, 187, 316, 268 } // _key Pad Accel. Toggle
+};
+
+static const char *const SETUP_STRS0[2] = { "off", "on" };
+static const char *const SETUP_STRS1[2] = { "Directly", "by Pixel" };
+static const char *const SETUP_STRS2[2] = { "Left", "Right" };
+static const char *const SETUP_STRS3[2] = { "Appear", "Slide" };
+static const char *const SETUP_STRS5[2] = { "Left", "Right" };
+static const char *const SETUP_NAMES[12] = {
+ "Exit", "M", "V", "S", "B", "New Font Style", "J", "Calibrate Joystick", "F", "W", "P", "K"
+};
+
+/*----------------------------------------------------------------*/
+
+void Settings::drawInteface(bool flag) {
+ People &people = *_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Sound &sound = *_vm->_sound;
+ Music &music = *_vm->_music;
+ UserInterface &ui = *_vm->_ui;
+ Common::String tempStr;
+
+ if (!flag) {
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 1), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1 + 1, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH,
+ SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR);
+ screen._backBuffer1.hLine(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 1, BORDER_COLOR);
+ screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH - 2,
+ SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND);
+ }
+
+ screen.makeButton(Common::Rect(SETUP_POINTS[0][0], SETUP_POINTS[0][1], SETUP_POINTS[0][2], SETUP_POINTS[0][1] + 10),
+ SETUP_POINTS[0][3] - screen.stringWidth("Exit") / 2, "Exit");
+
+ tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]);
+ screen.makeButton(Common::Rect(SETUP_POINTS[1][0], SETUP_POINTS[1][1], SETUP_POINTS[1][2], SETUP_POINTS[1][1] + 10),
+ SETUP_POINTS[1][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = Common::String::format("Voices %s", SETUP_STRS0[sound._voices]);
+ screen.makeButton(Common::Rect(SETUP_POINTS[2][0], SETUP_POINTS[2][1], SETUP_POINTS[2][2], SETUP_POINTS[2][1] + 10),
+ SETUP_POINTS[2][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = Common::String::format("Sound Effects %s", SETUP_STRS0[sound._digitized]);
+ screen.makeButton(Common::Rect(SETUP_POINTS[3][0], SETUP_POINTS[3][1], SETUP_POINTS[3][2], SETUP_POINTS[3][1] + 10),
+ SETUP_POINTS[3][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = Common::String::format("Auto Help %s", SETUP_STRS5[ui._helpStyle]);
+ screen.makeButton(Common::Rect(SETUP_POINTS[4][0], SETUP_POINTS[4][1], SETUP_POINTS[4][2], SETUP_POINTS[4][1] + 10),
+ SETUP_POINTS[4][3] - screen.stringWidth(tempStr) / 2, tempStr);
+ screen.makeButton(Common::Rect(SETUP_POINTS[5][0], SETUP_POINTS[5][1], SETUP_POINTS[5][2], SETUP_POINTS[5][1] + 10),
+ SETUP_POINTS[5][3] - screen.stringWidth("New Font Style") / 2, "New Font Style");
+
+ // WORKAROUND: We don't support the joystick in ScummVM, so draw the next two buttons as disabled
+ tempStr = "Joystick Off";
+ screen.makeButton(Common::Rect(SETUP_POINTS[6][0], SETUP_POINTS[6][1], SETUP_POINTS[6][2], SETUP_POINTS[6][1] + 10),
+ SETUP_POINTS[6][3] - screen.stringWidth(tempStr) / 2, tempStr);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[6][3], SETUP_POINTS[6][1]), COMMAND_NULL, false, tempStr);
+
+ tempStr = "Calibrate Joystick";
+ screen.makeButton(Common::Rect(SETUP_POINTS[7][0], SETUP_POINTS[7][1], SETUP_POINTS[7][2], SETUP_POINTS[7][1] + 10),
+ SETUP_POINTS[7][3] - screen.stringWidth(tempStr) / 2, tempStr);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[7][3], SETUP_POINTS[7][1]), COMMAND_NULL, false, tempStr);
+
+ tempStr = Common::String::format("Fade %s", screen._fadeStyle ? "by Pixel" : "Directly");
+ screen.makeButton(Common::Rect(SETUP_POINTS[8][0], SETUP_POINTS[8][1], SETUP_POINTS[8][2], SETUP_POINTS[8][1] + 10),
+ SETUP_POINTS[8][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = Common::String::format("Windows %s", ui._slideWindows ? "Slide" : "Appear");
+ screen.makeButton(Common::Rect(SETUP_POINTS[9][0], SETUP_POINTS[9][1], SETUP_POINTS[9][2], SETUP_POINTS[9][1] + 10),
+ SETUP_POINTS[9][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = Common::String::format("Portraits %s", SETUP_STRS0[people._portraitsOn]);
+ screen.makeButton(Common::Rect(SETUP_POINTS[10][0], SETUP_POINTS[10][1], SETUP_POINTS[10][2], SETUP_POINTS[10][1] + 10),
+ SETUP_POINTS[10][3] - screen.stringWidth(tempStr) / 2, tempStr);
+
+ tempStr = "Key Pad Slow";
+ screen.makeButton(Common::Rect(SETUP_POINTS[11][0], SETUP_POINTS[11][1], SETUP_POINTS[11][2], SETUP_POINTS[11][1] + 10),
+ SETUP_POINTS[11][3] - screen.stringWidth(tempStr) / 2, tempStr);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[11][3], SETUP_POINTS[11][1]), COMMAND_NULL, false, tempStr);
+
+ // Show the window immediately, or slide it on-screen
+ if (!flag) {
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow(true, CONTROLS_Y1);
+ }
+
+ ui._windowOpen = true;
+ } else {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ }
+}
+
+int Settings::drawButtons(const Common::Point &pt, int _key) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen;
+ Music &music = *_vm->_music;
+ Sound &sound = *_vm->_sound;
+ UserInterface &ui = *_vm->_ui;
+ int found = -1;
+ byte color;
+ Common::String tempStr;
+
+ for (int idx = 0; idx < 12; ++idx) {
+ if ((pt.x > SETUP_POINTS[idx][0] && pt.x < SETUP_POINTS[idx][2] && pt.y > SETUP_POINTS[idx][1]
+ && pt.y < (SETUP_POINTS[idx][1] + 10) && (events._pressed || events._released))
+ || (_key == SETUP_NAMES[idx][0])) {
+ found = idx;
+ color = COMMAND_HIGHLIGHTED;
+ } else {
+ color = COMMAND_FOREGROUND;
+ }
+
+ // Print the button text
+ switch (idx) {
+ case 1:
+ tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 2:
+ tempStr = Common::String::format("Voices %s", SETUP_STRS0[sound._voices]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 3:
+ tempStr = Common::String::format("Sound Effects %s", SETUP_STRS0[sound._digitized]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 4:
+ tempStr = Common::String::format("Auto Help %s", SETUP_STRS2[ui._helpStyle]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 6:
+ tempStr = "Joystick Off";
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr);
+ break;
+ case 7:
+ tempStr = "Calibrate Joystick";
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr);
+ break;
+ case 8:
+ tempStr = Common::String::format("Fade %s", SETUP_STRS1[screen._fadeStyle]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 9:
+ tempStr = Common::String::format("Windows %s", SETUP_STRS3[ui._slideWindows]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 10:
+ tempStr = Common::String::format("Portraits %s", SETUP_STRS0[people._portraitsOn]);
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr);
+ break;
+ case 11:
+ tempStr = "Key Pad Slow";
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr);
+ break;
+ default:
+ screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, SETUP_NAMES[idx]);
+ break;
+ }
+ }
+
+ return found;
+}
+
+void Settings::show(SherlockEngine *vm) {
+ Events &events = *vm->_events;
+ People &people = *vm->_people;
+ Scene &scene = *vm->_scene;
+ Screen &screen = *vm->_screen;
+ Sound &sound = *vm->_sound;
+ Music &music = *vm->_music;
+ Talk &talk = *vm->_talk;
+ ScalpelUserInterface &ui = *(ScalpelUserInterface *)vm->_ui;
+ bool updateConfig = false;
+
+ assert(vm->getGameID() == GType_SerratedScalpel);
+ Settings settings(vm);
+ settings.drawInteface(false);
+
+ do {
+ if (ui._menuCounter)
+ ui.whileMenuCounter();
+
+ int found = -1;
+ ui._key = -1;
+
+ scene.doBgAnim();
+ if (talk._talkToAbort)
+ return;
+
+ events.setButtonState();
+ Common::Point pt = events.mousePos();
+
+ if (events._pressed || events._released || events.kbHit()) {
+ ui.clearInfo();
+ ui._key = -1;
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ ui._key = toupper(keyState.keycode);
+
+ if (ui._key == Common::KEYCODE_RETURN || ui._key == Common::KEYCODE_SPACE) {
+ events._pressed = false;
+ events._oldButtons = 0;
+ ui._keyPress = '\0';
+ events._released = true;
+ }
+ }
+
+ // Handle highlighting button under mouse
+ found = settings.drawButtons(pt, ui._key);
+ }
+
+ if ((found == 0 && events._released) || (ui._key == 'E' || ui._key == Common::KEYCODE_ESCAPE))
+ // Exit
+ break;
+
+ if ((found == 1 && events._released) || ui._key == 'M') {
+ // Toggle music
+ music._musicOn = !music._musicOn;
+ if (!music._musicOn)
+ music.stopMusic();
+ else
+ music.startSong();
+
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 2 && events._released) || ui._key == 'V') {
+ sound._voices = !sound._voices;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 3 && events._released) || ui._key == 'S') {
+ // Toggle sound effects
+ sound._digitized = !sound._digitized;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 4 && events._released) || ui._key == 'A') {
+ // Help button style
+ ui._helpStyle = !ui._helpStyle;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 5 && events._released) || ui._key == 'N') {
+ // New font style
+ int fontNum = screen.fontNumber() + 1;
+ if (fontNum == 3)
+ fontNum = 0;
+
+ screen.setFont(fontNum);
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 8 && events._released) || ui._key == 'F') {
+ // Toggle fade style
+ screen._fadeStyle = !screen._fadeStyle;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 9 && events._released) || ui._key == 'W') {
+ // Window style
+ ui._slideWindows = !ui._slideWindows;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+
+ if ((found == 10 && events._released) || ui._key == 'P') {
+ // Toggle portraits being shown
+ people._portraitsOn = !people._portraitsOn;
+ updateConfig = true;
+ settings.drawInteface(true);
+ }
+ } while (!vm->shouldQuit());
+
+ ui.banishWindow();
+
+ if (updateConfig)
+ vm->saveConfig();
+
+ ui._keyPress = '\0';
+ ui._keyboardInput = false;
+ ui._windowBounds.top = CONTROLS_Y1;
+ ui._key = -1;
+}
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/settings.h b/engines/sherlock/scalpel/settings.h
new file mode 100644
index 0000000000..ff2e647a62
--- /dev/null
+++ b/engines/sherlock/scalpel/settings.h
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SETTINGS_H
+#define SHERLOCK_SETTINGS_H
+
+#include "common/scummsys.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Scalpel {
+
+class Settings {
+private:
+ SherlockEngine *_vm;
+
+ Settings(SherlockEngine *vm) : _vm(vm) {}
+
+ /**
+ * Draws the interface for the settings window
+ */
+ void drawInteface(bool flag);
+
+ /**
+ * Draws the buttons for the settings dialog
+ */
+ int drawButtons(const Common::Point &pt, int key);
+public:
+ /**
+ * Handles input when the settings window is being shown
+ * @remarks Whilst this would in theory be better in the Journal class, since it displays in
+ * the user interface, it uses so many internal UI fields, that it sort of made some sense
+ * to put it in the UserInterface class.
+ */
+ static void show(SherlockEngine *vm);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp
new file mode 100644
index 0000000000..4eab01947a
--- /dev/null
+++ b/engines/sherlock/scalpel/tsage/logo.cpp
@@ -0,0 +1,684 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "sherlock/scalpel/tsage/logo.h"
+#include "sherlock/scalpel/scalpel.h"
+
+namespace Sherlock {
+namespace Scalpel {
+namespace TsAGE {
+
+TLib *Visage::_tLib;
+
+Visage::Visage() {
+ _resNum = -1;
+ _rlbNum = -1;
+ _stream = nullptr;
+}
+
+void Visage::setVisage(int resNum, int rlbNum) {
+ if ((_resNum != resNum) || (_rlbNum != rlbNum)) {
+ _resNum = resNum;
+ _rlbNum = rlbNum;
+ delete _stream;
+
+ // Games after Ringworld have an extra indirection via the visage index file
+ Common::SeekableReadStream *stream = _tLib->getResource(RES_VISAGE, resNum, 9999);
+ if (rlbNum == 0)
+ rlbNum = 1;
+
+ // Check how many slots there are
+ uint16 count = stream->readUint16LE();
+ if (rlbNum > count)
+ rlbNum = count;
+
+ // Get the flags/rlbNum to use
+ stream->seek((rlbNum - 1) * 4 + 2);
+ uint32 v = stream->readUint32LE();
+ int flags = v >> 30;
+
+ if (flags & 3) {
+ rlbNum = (int)(v & 0xff);
+ }
+ assert((flags & 3) == 0);
+ delete stream;
+
+ _stream = _tLib->getResource(RES_VISAGE, resNum, rlbNum);
+ }
+}
+
+void Visage::clear() {
+ delete _stream;
+ _stream = nullptr;
+}
+
+Visage::~Visage() {
+ delete _stream;
+}
+
+void Visage::getFrame(ObjectSurface &s, int frameNum) {
+ _stream->seek(0);
+ int numFrames = _stream->readUint16LE();
+ if (frameNum > numFrames)
+ frameNum = numFrames;
+ if (frameNum > 0)
+ --frameNum;
+
+ _stream->seek(frameNum * 4 + 2);
+ int offset = _stream->readUint32LE();
+ _stream->seek(offset);
+
+ surfaceFromRes(s);
+}
+
+int Visage::getFrameCount() const {
+ _stream->seek(0);
+ return _stream->readUint16LE();
+}
+
+bool Visage::isLoaded() const {
+ return _stream != nullptr;
+}
+
+void Visage::surfaceFromRes(ObjectSurface &s) {
+ int frameWidth = _stream->readUint16LE();
+ int frameHeight = _stream->readUint16LE();
+ Common::Rect r(0, 0, frameWidth, frameHeight);
+ s.create(r.width(), r.height());
+
+ s._centroid.x = _stream->readSint16LE();
+ s._centroid.y = _stream->readSint16LE();
+
+ _stream->skip(1);
+ byte flags = _stream->readByte();
+ bool rleEncoded = (flags & 2) != 0;
+
+ byte *destP = (byte *)s.getPixels();
+
+ if (!rleEncoded) {
+ _stream->read(destP, r.width() * r.height());
+ } else {
+ Common::fill(destP, destP + (r.width() * r.height()), 0xff);
+
+ for (int yp = 0; yp < r.height(); ++yp) {
+ int width = r.width();
+ destP = (byte *)s.getBasePtr(0, yp);
+
+ while (width > 0) {
+ uint8 controlVal = _stream->readByte();
+ if ((controlVal & 0x80) == 0) {
+ // Copy specified number of bytes
+ _stream->read(destP, controlVal);
+ width -= controlVal;
+ destP += controlVal;
+ } else if ((controlVal & 0x40) == 0) {
+ // Skip a specified number of output pixels
+ destP += controlVal & 0x3f;
+ width -= controlVal & 0x3f;
+ } else {
+ // Copy a specified pixel a given number of times
+ controlVal &= 0x3f;
+ int pixel = _stream->readByte();
+
+ Common::fill(destP, destP + controlVal, pixel);
+ destP += controlVal;
+ width -= controlVal;
+ }
+ }
+ assert(width == 0);
+ }
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+ScalpelEngine *Object::_vm;
+
+Object::Object() {
+ _vm = nullptr;
+ _isAnimating = _finished = false;
+ _frame = 0;
+ _numFrames = 0;
+ _frameChange = 0;
+ _angle = _changeCtr = 0;
+ _walkStartFrame = 0;
+ _majorDiff = _minorDiff = 0;
+}
+
+void Object::setVisage(int visage, int strip) {
+ _visage.setVisage(visage, strip);
+}
+
+void Object::setAnimMode(bool isAnimating) {
+ _isAnimating = isAnimating;
+ _finished = false;
+
+ _updateStartFrame = _vm->_events->getFrameCounter();
+ if (_numFrames)
+ _updateStartFrame += 60 / _numFrames;
+ _frameChange = 1;
+}
+
+void Object::setDestination(const Common::Point &pt) {
+ _destination = pt;
+
+ int moveRate = 10;
+ _walkStartFrame = _vm->_events->getFrameCounter();
+ _walkStartFrame += 60 / moveRate;
+
+ calculateMoveAngle();
+
+ // Get the difference
+ int diffX = _destination.x - _position.x;
+ int diffY = _destination.y - _position.y;
+ int xSign = (diffX < 0) ? -1 : (diffX > 0 ? 1 : 0);
+ int ySign = (diffY < 0) ? -1 : (diffY > 0 ? 1 : 0);
+ diffX = ABS(diffX);
+ diffY = ABS(diffY);
+
+ if (diffX < diffY) {
+ _minorDiff = diffX / 2;
+ _majorDiff = diffY;
+ } else {
+ _minorDiff = diffY / 2;
+ _majorDiff = diffX;
+ }
+
+ // Set the destination position
+ _moveDelta = Common::Point(diffX, diffY);
+ _moveSign = Common::Point(xSign, ySign);
+ _changeCtr = 0;
+
+ assert(diffX || diffY);
+}
+
+void Object::erase() {
+ Screen &screen = *_vm->_screen;
+
+ if (_visage.isLoaded() && !_oldBounds.isEmpty())
+ screen.blitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds);
+}
+
+void Object::update() {
+ Screen &screen = *_vm->_screen;
+
+ if (_visage.isLoaded()) {
+ if (isMoving()) {
+ uint32 currTime = _vm->_events->getFrameCounter();
+ if (_walkStartFrame <= currTime) {
+ int moveRate = 10;
+ int frameInc = 60 / moveRate;
+ _walkStartFrame = currTime + frameInc;
+ move();
+ }
+ }
+
+ if (_isAnimating) {
+ if (_frame < _visage.getFrameCount())
+ _frame = changeFrame();
+ else
+ _finished = true;
+ }
+
+ // Get the new frame
+ ObjectSurface s;
+ _visage.getFrame(s, _frame);
+
+ // Display the frame
+ _oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.w(), _position.y + s.h());
+ _oldBounds.translate(-s._centroid.x, -s._centroid.y);
+ screen.transBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top));
+ }
+}
+
+int Object::changeFrame() {
+ int frameNum = _frame;
+ uint32 currentFrame = _vm->_events->getFrameCounter();
+
+ if (_updateStartFrame <= currentFrame) {
+ if (_numFrames > 0) {
+ int v = 60 / _numFrames;
+ _updateStartFrame = currentFrame + v;
+
+ frameNum = getNewFrame();
+ }
+ }
+
+ return frameNum;
+}
+
+int Object::getNewFrame() {
+ int frameNum = _frame + _frameChange;
+
+ if (_frameChange > 0) {
+ if (frameNum > _visage.getFrameCount()) {
+ frameNum = 1;
+ }
+ } else if (frameNum < 1) {
+ frameNum = _visage.getFrameCount();
+ }
+
+ return frameNum;
+}
+
+void Object::calculateMoveAngle() {
+ int xDiff = _destination.x - _position.x, yDiff = _position.y - _destination.y;
+
+ if (!xDiff && !yDiff)
+ _angle = 0;
+ else if (!xDiff)
+ _angle = (_destination.y >= _position.y) ? 180 : 0;
+ else if (!yDiff)
+ _angle = (_destination.x >= _position.x) ? 90 : 270;
+ else {
+ int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100);
+
+ if (yDiff < 0)
+ result = 180 - result;
+ else if (xDiff < 0)
+ result += 360;
+
+ _angle = result;
+ }
+}
+
+bool Object::isAnimEnded() const {
+ return _finished;
+}
+
+bool Object::isMoving() const {
+ return (_destination.x != 0) && (_destination != _position);
+}
+
+void Object::move() {
+ Common::Point currPos = _position;
+ Common::Point moveDiff(5, 3);
+ int percent = 100;
+
+ if (dontMove())
+ return;
+
+ if (_moveDelta.x >= _moveDelta.y) {
+ int xAmount = _moveSign.x * moveDiff.x * percent / 100;
+ if (!xAmount)
+ xAmount = _moveSign.x;
+ currPos.x += xAmount;
+
+ int yAmount = ABS(_destination.y - currPos.y);
+ int yChange = _majorDiff / ABS(xAmount);
+ int ySign;
+
+ if (!yChange)
+ ySign = _moveSign.y;
+ else {
+ int v = yAmount / yChange;
+ _changeCtr += yAmount % yChange;
+ if (_changeCtr >= yChange) {
+ ++v;
+ _changeCtr -= yChange;
+ }
+
+ ySign = _moveSign.y * v;
+ }
+
+ currPos.y += ySign;
+ _majorDiff -= ABS(xAmount);
+ } else {
+ int yAmount = _moveSign.y * moveDiff.y * percent / 100;
+ if (!yAmount)
+ yAmount = _moveSign.y;
+ currPos.y += yAmount;
+
+ int xAmount = ABS(_destination.x - currPos.x);
+ int xChange = _majorDiff / ABS(yAmount);
+ int xSign;
+
+ if (!xChange)
+ xSign = _moveSign.x;
+ else {
+ int v = xAmount / xChange;
+ _changeCtr += xAmount % xChange;
+ if (_changeCtr >= xChange) {
+ ++v;
+ _changeCtr -= xChange;
+ }
+
+ xSign = _moveSign.x * v;
+ }
+
+ currPos.x += xSign;
+ _majorDiff -= ABS(yAmount);
+ }
+
+ _position = currPos;
+ if (dontMove())
+ _position = _destination;
+}
+
+bool Object::dontMove() const {
+ return (_majorDiff <= 0);
+}
+
+void Object::endMove() {
+ _position = _destination;
+}
+
+/*----------------------------------------------------------------*/
+
+bool Logo::show(ScalpelEngine *vm) {
+ Events &events = *vm->_events;
+ Logo *logo = new Logo(vm);
+ bool interrupted = false;
+
+ while (!logo->finished()) {
+ logo->nextFrame();
+
+ // Erase areas from previous frame, and update and re-draw objects
+ for (int idx = 0; idx < 4; ++idx)
+ logo->_objects[idx].erase();
+ for (int idx = 0; idx < 4; ++idx)
+ logo->_objects[idx].update();
+
+ events.wait(2);
+ events.setButtonState();
+
+ interrupted = vm->shouldQuit() || events.kbHit() || events._pressed;
+ if (interrupted) {
+ // Keyboard or mouse button pressed, so break out of logo display
+ events.clearEvents();
+ break;
+ }
+ }
+
+ delete logo;
+ return !interrupted;
+}
+
+Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") {
+ Object::_vm = vm;
+ Visage::_tLib = &_lib;
+
+ _finished = false;
+
+ // Initialize counter
+ _counter = 0;
+
+ // Initialize wait frame counters
+ _waitFrames = 0;
+ _waitStartFrame = 0;
+
+ // Initialize animation counters
+ _animateObject = 0;
+ _animateStartFrame = 0;
+ _animateFrameDelay = 0;
+ _animateFrames = NULL;
+ _animateFrame = 0;
+
+ // Save a copy of the original palette
+ _vm->_screen->getPalette(_originalPalette);
+
+ // Set up the palettes
+ Common::fill(&_palette1[0], &_palette1[PALETTE_SIZE], 0);
+ Common::fill(&_palette1[0], &_palette2[PALETTE_SIZE], 0);
+ Common::fill(&_palette1[0], &_palette3[PALETTE_SIZE], 0);
+
+ _lib.getPalette(_palette1, 1111);
+ _lib.getPalette(_palette1, 10);
+ _lib.getPalette(_palette2, 1111);
+ _lib.getPalette(_palette2, 1);
+ _lib.getPalette(_palette3, 1111);
+ _lib.getPalette(_palette3, 14);
+}
+
+Logo::~Logo() {
+ // Restore the original palette
+ _vm->_screen->setPalette(_originalPalette);
+}
+
+bool Logo::finished() const {
+ return _finished;
+}
+
+const AnimationFrame handFrames[] = {
+ { 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 },
+ { 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 },
+ { 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 },
+ { 11, 158, 121 }, { 0, 0, 0 }
+};
+
+const AnimationFrame companyFrames[] = {
+ { 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 },
+ { 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 },
+ { 0, 0, 0 }
+};
+
+void Logo::nextFrame() {
+ Screen &screen = *_vm->_screen;
+
+ if (_waitFrames) {
+ uint32 currFrame = _vm->_events->getFrameCounter();
+ if (currFrame - _waitStartFrame < _waitFrames) {
+ return;
+ }
+ _waitStartFrame = 0;
+ _waitFrames = 0;
+ }
+
+ if (_animateFrames) {
+ uint32 currFrame = _vm->_events->getFrameCounter();
+ if (currFrame > _animateStartFrame + _animateFrameDelay) {
+ AnimationFrame animationFrame = _animateFrames[_animateFrame];
+ if (animationFrame.frame) {
+ _objects[_animateObject]._frame = animationFrame.frame;
+ _objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y);
+ _animateStartFrame += _animateFrameDelay;
+ _animateFrame++;
+ } else {
+ _animateObject = 0;
+ _animateFrameDelay = 0;
+ _animateFrames = NULL;
+ _animateStartFrame = 0;
+ _animateFrame = 0;
+ }
+ }
+ if (_animateFrames)
+ return;
+ }
+
+ switch (_counter++) {
+ case 0:
+ // Load the background and fade it in
+ loadBackground();
+ fade(_palette1);
+ break;
+
+ case 1:
+ // First half of square, circle, and triangle arranging themselves
+ _objects[0].setVisage(16, 1);
+ _objects[0]._frame = 1;
+ _objects[0]._position = Common::Point(169, 107);
+ _objects[0]._numFrames = 7;
+ _objects[0].setAnimMode(true);
+ break;
+
+ case 2:
+ // Keep waiting until first animation ends
+ if (!_objects[0].isAnimEnded()) {
+ --_counter;
+ } else {
+ // Start second half of the shapes animation
+ _objects[0].setVisage(16, 2);
+ _objects[0]._frame = 1;
+ _objects[0]._numFrames = 11;
+ _objects[0].setAnimMode(true);
+ }
+ break;
+
+ case 3:
+ // Keep waiting until second animation of shapes ordering themselves ends
+ if (!_objects[0].isAnimEnded()) {
+ --_counter;
+ } else {
+ // Fade out the background but keep the shapes visible
+ fade(_palette2);
+ screen._backBuffer1.clear();
+ }
+ waitFrames(10);
+ break;
+
+ case 4:
+ // Load the new palette
+ byte palette[PALETTE_SIZE];
+ Common::copy(&_palette2[0], &_palette2[PALETTE_SIZE], &palette[0]);
+ _lib.getPalette(palette, 12);
+ screen.clear();
+ screen.setPalette(palette);
+
+ // Morph into the EA logo
+ _objects[0].setVisage(12, 1);
+ _objects[0]._frame = 1;
+ _objects[0]._numFrames = 7;
+ _objects[0].setAnimMode(true);
+ _objects[0]._position = Common::Point(170, 142);
+ _objects[0].setDestination(Common::Point(158, 71));
+ break;
+
+ case 5:
+ // Wait until the logo has expanded upwards to form EA logo
+ if (_objects[0].isMoving())
+ --_counter;
+ break;
+
+ case 6:
+ fade(_palette3, 40);
+ break;
+
+ case 7:
+ // Show the 'Electronic Arts' company name
+ _objects[1].setVisage(14, 1);
+ _objects[1]._frame = 1;
+ _objects[1]._position = Common::Point(152, 98);
+ waitFrames(120);
+ break;
+
+ case 8:
+ // Start sequence of positioning and size hand cursor in an arc
+ _objects[2].setVisage(18, 1);
+ startAnimation(2, 5, &handFrames[0]);
+ break;
+
+ case 9:
+ // Show a highlighting of the company name
+ _objects[1].remove();
+ _objects[2].erase();
+ _objects[2].remove();
+ _objects[3].setVisage(19, 1);
+ startAnimation(3, 8, &companyFrames[0]);
+ break;
+
+ case 10:
+ waitFrames(180);
+ break;
+
+ case 11:
+ _finished = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void Logo::waitFrames(uint frames) {
+ _waitFrames = frames;
+ _waitStartFrame = _vm->_events->getFrameCounter();
+}
+
+void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) {
+ _animateObject = object;
+ _animateFrameDelay = frameDelay;
+ _animateFrames = frames;
+ _animateStartFrame = _vm->_events->getFrameCounter();
+ _animateFrame = 1;
+
+ _objects[object]._frame = frames[0].frame;
+ _objects[object]._position = Common::Point(frames[0].x, frames[0].y);
+}
+
+void Logo::loadBackground() {
+ Screen &screen = *_vm->_screen;
+
+ for (int idx = 0; idx < 4; ++idx) {
+ // Get the portion of the screen
+ Common::SeekableReadStream *stream = _lib.getResource(RES_BITMAP, 10, idx);
+
+ // Load it onto the surface
+ Common::Point pt((idx / 2) * (SHERLOCK_SCREEN_WIDTH / 2), (idx % 2) * (SHERLOCK_SCREEN_HEIGHT / 2));
+ for (int y = 0; y < (SHERLOCK_SCREEN_HEIGHT / 2); ++y, ++pt.y) {
+ byte *pDest = (byte *)screen._backBuffer1.getBasePtr(pt.x, pt.y);
+ stream->read(pDest, SHERLOCK_SCREEN_WIDTH / 2);
+ }
+
+ // _backgroundBounds = Rect(0, 0, READ_LE_UINT16(data), READ_LE_UINT16(data + 2));
+ delete stream;
+ }
+
+ // Default to a blank palette
+ byte palette[PALETTE_SIZE];
+ Common::fill(&palette[0], &palette[PALETTE_SIZE], 0);
+ screen.setPalette(palette);
+
+ // Copy the surface to the screen
+ screen.blitFrom(screen._backBuffer1);
+}
+
+void Logo::fade(const byte palette[PALETTE_SIZE], int step) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ byte startPalette[PALETTE_SIZE];
+ byte tempPalette[PALETTE_SIZE];
+
+ screen.getPalette(startPalette);
+
+ for (int percent = 0; percent < 100; percent += step) {
+ for (int palIndex = 0; palIndex < 256; ++palIndex) {
+ const byte *pal1P = (const byte *)&startPalette[palIndex * 3];
+ const byte *pal2P = (const byte *)&palette[palIndex * 3];
+ byte *destP = &tempPalette[palIndex * 3];
+
+ for (int rgbIndex = 0; rgbIndex < 3; ++rgbIndex, ++pal1P, ++pal2P, ++destP) {
+ *destP = (int)*pal1P + ((int)*pal2P - (int)*pal1P) * percent / 100;
+ }
+ }
+
+ screen.setPalette(tempPalette);
+ events.wait(1);
+ }
+
+ // Set final palette
+ screen.setPalette(palette);
+}
+
+} // end of namespace TsAGE
+} // end of namespace Scalpel
+} // end of namespace Sherlock
diff --git a/engines/sherlock/scalpel/tsage/logo.h b/engines/sherlock/scalpel/tsage/logo.h
new file mode 100644
index 0000000000..c9fac00d9c
--- /dev/null
+++ b/engines/sherlock/scalpel/tsage/logo.h
@@ -0,0 +1,250 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_TSAGE_LOGO_H
+#define SHERLOCK_SCALPEL_TSAGE_LOGO_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "common/list.h"
+#include "common/str.h"
+#include "common/str-array.h"
+#include "common/util.h"
+#include "graphics/surface.h"
+#include "sherlock/scalpel/tsage/resources.h"
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+namespace Scalpel {
+
+class ScalpelEngine;
+
+namespace TsAGE {
+
+class ObjectSurface : public Surface {
+public:
+ Common::Point _centroid;
+public:
+ ObjectSurface() : Surface() {}
+ virtual ~ObjectSurface() {}
+};
+
+class Visage {
+private:
+ Common::SeekableReadStream *_stream;
+
+ /**
+ * Translates a raw image resource into a graphics surface
+ */
+ void surfaceFromRes(ObjectSurface &s);
+public:
+ static TLib *_tLib;
+ int _resNum;
+ int _rlbNum;
+public:
+ Visage();
+ ~Visage();
+
+ /**
+ * Set the visage number
+ */
+ void setVisage(int resNum, int rlbNum = 9999);
+
+ /**
+ * Clear the visage
+ */
+ void clear();
+
+ /**
+ * Get a frame from the visage
+ */
+ void getFrame(ObjectSurface &s, int frameNum);
+
+ /**
+ * Return the number of frames
+ */
+ int getFrameCount() const;
+
+ /**
+ * Returns whether the visage is loaded
+ */
+ bool isLoaded() const;
+};
+
+class Object {
+private:
+ Visage _visage;
+ uint32 _updateStartFrame;
+ bool _isAnimating;
+ bool _finished;
+ uint32 _walkStartFrame;
+ int _angle;
+ int _changeCtr;
+ int _majorDiff, _minorDiff;
+ Common::Point _moveDelta;
+ Common::Point _moveSign;
+
+ /**
+ * Return the next frame when the object is animating
+ */
+ int changeFrame();
+
+ /**
+ * Gets the next frame in the sequence
+ */
+ int getNewFrame();
+
+ /**
+ * Calculate the angle between the current position and a designated destination
+ */
+ void calculateMoveAngle();
+
+ /**
+ * Handle any object movement
+ */
+ void move();
+
+ /**
+ * Returns whether not to make any movement
+ */
+ bool dontMove() const;
+
+ /**
+ * Ends any current movement
+ */
+ void endMove();
+public:
+ static ScalpelEngine *_vm;
+ Common::Point _position;
+ Common::Point _destination;
+ Common::Rect _oldBounds;
+ int _frame;
+ int _numFrames;
+ int _frameChange;
+public:
+ Object();
+
+ /**
+ * Load the data for the object
+ */
+ void setVisage(int visage, int strip);
+
+ /**
+ * Sets whether the object is animating
+ */
+ void setAnimMode(bool isAnimating);
+
+ /**
+ * Starts an object moving to a given destination
+ */
+ void setDestination(const Common::Point &pt);
+
+ /**
+ * Returns true if an animation is ended
+ */
+ bool isAnimEnded() const;
+
+ /**
+ * Return true if object is moving
+ */
+ bool isMoving() const;
+
+ /**
+ * Erase the area the object was previously drawn at, by restoring the background
+ */
+ void erase();
+
+ /**
+ * Update the frame
+ */
+ void update();
+
+ /**
+ * Remove an object from being displayed
+ */
+ void remove() { _visage.clear(); }
+};
+
+struct AnimationFrame {
+ int frame;
+ int x;
+ int y;
+};
+
+class Logo {
+private:
+ ScalpelEngine *_vm;
+ TLib _lib;
+ int _counter;
+ bool _finished;
+ byte _originalPalette[PALETTE_SIZE];
+ byte _palette1[PALETTE_SIZE];
+ byte _palette2[PALETTE_SIZE];
+ byte _palette3[PALETTE_SIZE];
+ Object _objects[4];
+ uint _waitFrames;
+ uint32 _waitStartFrame;
+ int _animateObject;
+ uint32 _animateStartFrame;
+ uint _animateFrameDelay;
+ const AnimationFrame *_animateFrames;
+ uint _animateFrame;
+
+ Logo(ScalpelEngine *vm);
+ ~Logo();
+
+ void nextFrame();
+
+ bool finished() const;
+
+ /**
+ * Wait for a number of frames. Note that the frame count in _events is
+ * not the same as the number of calls to nextFrame().
+ */
+ void waitFrames(uint frames);
+
+ /**
+ * Start an animation sequence. Used for sequences that are described
+ * one frame at a time because they do unusual things, or run at
+ * unusual rates.
+ */
+ void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames);
+
+ /**
+ * Load the background for the scene
+ */
+ void loadBackground();
+
+ /**
+ * Fade from the current palette to a new one
+ */
+ void fade(const byte palette[PALETTE_SIZE], int step = 6);
+public:
+ static bool show(ScalpelEngine *vm);
+};
+
+} // end of namespace TsAGE
+} // end of namespace Scalpel
+} // end of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scalpel/tsage/resources.cpp b/engines/sherlock/scalpel/tsage/resources.cpp
new file mode 100644
index 0000000000..56b7021563
--- /dev/null
+++ b/engines/sherlock/scalpel/tsage/resources.cpp
@@ -0,0 +1,373 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/memstream.h"
+#include "common/stack.h"
+#include "sherlock/scalpel/tsage/resources.h"
+
+namespace Sherlock {
+namespace Scalpel {
+namespace TsAGE {
+
+static uint16 bitMasks[4] = {0x1ff, 0x3ff, 0x7ff, 0xfff};
+
+uint16 BitReader::readToken() {
+ assert((numBits >= 9) && (numBits <= 12));
+ uint16 result = _remainder;
+ int bitsLeft = numBits - _bitsLeft;
+ int bitOffset = _bitsLeft;
+ _bitsLeft = 0;
+
+ while (bitsLeft >= 0) {
+ _remainder = readByte();
+ result |= _remainder << bitOffset;
+ bitsLeft -= 8;
+ bitOffset += 8;
+ }
+
+ _bitsLeft = -bitsLeft;
+ _remainder >>= 8 - _bitsLeft;
+ return result & bitMasks[numBits - 9];
+}
+
+/*-------------------------------------------------------------------------*/
+
+TLib::TLib(const Common::String &filename) : _filename(filename) {
+
+ // If the resource strings list isn't yet loaded, load them
+ if (_resStrings.size() == 0) {
+ Common::File f;
+ if (f.open("tsage.cfg")) {
+ while (!f.eos()) {
+ _resStrings.push_back(f.readLine());
+ }
+ f.close();
+ }
+ }
+
+ if (!_file.open(filename))
+ error("Missing file %s", filename.c_str());
+
+ loadIndex();
+}
+
+TLib::~TLib() {
+ _resStrings.clear();
+}
+
+/**
+ * Load a section index from the given position in the file
+ */
+void TLib::loadSection(uint32 fileOffset) {
+ _resources.clear();
+ _file.seek(fileOffset);
+ _sections.fileOffset = fileOffset;
+
+ loadSection(_file, _resources);
+}
+
+struct DecodeReference {
+ uint16 vWord;
+ uint8 vByte;
+};
+
+/**
+ * Gets a resource from the currently loaded section
+ */
+Common::SeekableReadStream *TLib::getResource(uint16 id, bool suppressErrors) {
+ // Scan for an entry for the given Id
+ ResourceEntry *re = nullptr;
+ ResourceList::iterator iter;
+ for (iter = _resources.begin(); iter != _resources.end(); ++iter) {
+ if ((*iter).id == id) {
+ re = &(*iter);
+ break;
+ }
+ }
+ if (!re) {
+ if (suppressErrors)
+ return nullptr;
+ error("Could not find resource Id #%d", id);
+ }
+
+ if (!re->isCompressed) {
+ // Read in the resource data and return it
+ byte *dataP = (byte *)malloc(re->size);
+ _file.seek(_sections.fileOffset + re->fileOffset);
+ _file.read(dataP, re->size);
+
+ return new Common::MemoryReadStream(dataP, re->size, DisposeAfterUse::YES);
+ }
+
+ /*
+ * Decompress the data block
+ */
+
+ _file.seek(_sections.fileOffset + re->fileOffset);
+ Common::ReadStream *compStream = _file.readStream(re->size);
+ BitReader bitReader(*compStream);
+
+ byte *dataOut = (byte *)malloc(re->uncompressedSize);
+ byte *destP = dataOut;
+ uint bytesWritten = 0;
+
+ uint16 ctrCurrent = 0x102, ctrMax = 0x200;
+ uint16 word_48050 = 0, currentToken = 0, word_48054 =0;
+ byte byte_49068 = 0, byte_49069 = 0;
+
+ const uint tableSize = 0x1000;
+ DecodeReference *table = (DecodeReference *)malloc(tableSize * sizeof(DecodeReference));
+ if (!table)
+ error("[TLib::getResource] Cannot allocate table buffer");
+
+ for (uint i = 0; i < tableSize; ++i) {
+ table[i].vByte = table[i].vWord = 0;
+ }
+ Common::Stack<uint16> tokenList;
+
+ for (;;) {
+ // Get the next decode token
+ uint16 token = bitReader.readToken();
+
+ // Handle the token
+ if (token == 0x101) {
+ // End of compressed stream
+ break;
+ } else if (token == 0x100) {
+ // Reset bit-rate
+ bitReader.numBits = 9;
+ ctrMax = 0x200;
+ ctrCurrent = 0x102;
+
+ // Set variables with next token
+ currentToken = word_48050 = bitReader.readToken();
+ byte_49069 = byte_49068 = (byte)currentToken;
+
+ ++bytesWritten;
+ assert(bytesWritten <= re->uncompressedSize);
+ *destP++ = byte_49069;
+ } else {
+ word_48054 = word_48050 = token;
+
+ if (token >= ctrCurrent) {
+ word_48050 = currentToken;
+ tokenList.push(byte_49068);
+ }
+
+ while (word_48050 >= 0x100) {
+ assert(word_48050 < 0x1000);
+ tokenList.push(table[word_48050].vByte);
+ word_48050 = table[word_48050].vWord;
+ }
+
+ byte_49069 = byte_49068 = (byte)word_48050;
+ tokenList.push(word_48050);
+
+ // Write out any cached tokens
+ while (!tokenList.empty()) {
+ ++bytesWritten;
+ assert(bytesWritten <= re->uncompressedSize);
+ *destP++ = tokenList.pop();
+ }
+
+ assert(ctrCurrent < 0x1000);
+ table[ctrCurrent].vByte = byte_49069;
+ table[ctrCurrent].vWord = currentToken;
+ ++ctrCurrent;
+
+ currentToken = word_48054;
+ if ((ctrCurrent >= ctrMax) && (bitReader.numBits != 12)) {
+ // Move to the next higher bit-rate
+ ++bitReader.numBits;
+ ctrMax <<= 1;
+ }
+ }
+ }
+
+ free(table);
+
+ assert(bytesWritten == re->uncompressedSize);
+ delete compStream;
+ return new Common::MemoryReadStream(dataOut, re->uncompressedSize, DisposeAfterUse::YES);
+}
+
+/**
+ * Finds the correct section and loads the specified resource within it
+ */
+Common::SeekableReadStream *TLib::getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors) {
+ SectionList::iterator i = _sections.begin();
+ while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum))
+ ++i;
+ if (i == _sections.end()) {
+ if (suppressErrors)
+ return nullptr;
+ error("Unknown resource type %d num %d", resType, resNum);
+ }
+
+ loadSection((*i).fileOffset);
+
+ return getResource(rlbNum, suppressErrors);
+}
+
+/**
+ * Gets the offset of the start of a resource in the resource file
+ */
+uint32 TLib::getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry) {
+ // Find the correct section
+ SectionList::iterator i = _sections.begin();
+ while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum))
+ ++i;
+ if (i == _sections.end()) {
+ error("Unknown resource type %d num %d", resType, resNum);
+ }
+
+ // Load in the section index
+ loadSection((*i).fileOffset);
+
+ // Scan for an entry for the given Id
+ ResourceEntry *re = nullptr;
+ ResourceList::iterator iter;
+ for (iter = _resources.begin(); iter != _resources.end(); ++iter) {
+ if ((*iter).id == rlbNum) {
+ re = &(*iter);
+ break;
+ }
+ }
+
+ // Throw an error if no resource was found, or the resource is compressed
+ if (!re || re->isCompressed)
+ error("Invalid resource Id #%d", rlbNum);
+
+ // Return the resource entry as well as the file offset
+ entry = *re;
+ return _sections.fileOffset + entry.fileOffset;
+}
+
+void TLib::loadIndex() {
+ uint16 resNum, configId, fileOffset;
+
+ // Load the root resources section
+ loadSection(0);
+
+ // Get the single resource from it
+ Common::SeekableReadStream *stream = getResource(0);
+
+ _sections.clear();
+
+ // Loop through reading the entries
+ while ((resNum = stream->readUint16LE()) != 0xffff) {
+ configId = stream->readUint16LE();
+ fileOffset = stream->readUint16LE();
+
+ SectionEntry se;
+ se.resNum = resNum;
+ se.resType = (ResourceType)(configId & 0x1f);
+ se.fileOffset = (((configId >> 5) & 0x7ff) << 16) | fileOffset;
+
+ _sections.push_back(se);
+ }
+
+ delete stream;
+}
+
+/**
+ * Retrieves the specified palette resource and returns it's data
+ *
+ * @paletteNum Specefies the palette number
+ */
+void TLib::getPalette(byte palette[PALETTE_SIZE], int paletteNum) {
+ // Get the specified palette
+ Common::SeekableReadStream *stream = getResource(RES_PALETTE, paletteNum, 0, true);
+ if (!stream)
+ return;
+
+ int startNum = stream->readUint16LE();
+ int numEntries = stream->readUint16LE();
+ assert((startNum < 256) && ((startNum + numEntries) <= 256));
+ stream->skip(2);
+
+ // Copy over the data
+ stream->read(&palette[startNum * 3], numEntries * 3);
+
+ delete stream;
+}
+
+/**
+ * Open up the given resource file using a passed file object. If the desired entry is found
+ * in the index, return the index entry for it, and move the file to the start of the resource
+ */
+bool TLib::scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum,
+ ResourceEntry &resEntry) {
+ // Load the root section index
+ ResourceList resList;
+ loadSection(f, resList);
+
+ // Loop through the index for the desired entry
+ ResourceList::iterator iter;
+ for (iter = resList.begin(); iter != resList.end(); ++iter) {
+ ResourceEntry &re = *iter;
+ if (re.id == resNum) {
+ // Found it, so exit
+ resEntry = re;
+ f.seek(re.fileOffset);
+ return true;
+ }
+ }
+
+ // No matching entry found
+ return false;
+}
+
+/**
+ * Inner logic for decoding a section index into a passed resource list object
+ */
+void TLib::loadSection(Common::File &f, ResourceList &resources) {
+ if (f.readUint32BE() != 0x544D492D)
+ error("Data block is not valid Rlb data");
+
+ /*uint8 unknown1 = */f.readByte();
+ uint16 numEntries = f.readByte();
+
+ for (uint i = 0; i < numEntries; ++i) {
+ uint16 id = f.readUint16LE();
+ uint16 size = f.readUint16LE();
+ uint16 uncSize = f.readUint16LE();
+ uint8 sizeHi = f.readByte();
+ uint8 type = f.readByte() >> 5;
+ assert(type <= 1);
+ uint32 offset = f.readUint32LE();
+
+ ResourceEntry re;
+ re.id = id;
+ re.fileOffset = offset;
+ re.isCompressed = type != 0;
+ re.size = ((sizeHi & 0xF) << 16) | size;
+ re.uncompressedSize = ((sizeHi & 0xF0) << 12) | uncSize;
+
+ resources.push_back(re);
+ }
+}
+
+} // end of namespace TsAGE
+} // end of namespace Scalpel
+} // end of namespace Sherlock
diff --git a/engines/sherlock/scalpel/tsage/resources.h b/engines/sherlock/scalpel/tsage/resources.h
new file mode 100644
index 0000000000..3e09b6b0b1
--- /dev/null
+++ b/engines/sherlock/scalpel/tsage/resources.h
@@ -0,0 +1,139 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCALPEL_TSAGE_RESOURCES_H
+#define SHERLOCK_SCALPEL_TSAGE_RESOURCES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "common/list.h"
+#include "common/str.h"
+#include "common/str-array.h"
+#include "common/util.h"
+#include "graphics/surface.h"
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+namespace Scalpel {
+namespace TsAGE {
+
+// Magic number used by original game to identify valid memory blocks
+const uint32 MEMORY_ENTRY_ID = 0xE11DA722;
+
+const int MEMORY_POOL_SIZE = 1000;
+
+enum ResourceType { RES_LIBRARY, RES_STRIP, RES_IMAGE, RES_PALETTE, RES_VISAGE, RES_SOUND, RES_MESSAGE,
+ RES_FONT, RES_POINTER, RES_BANK, RES_SND_DRIVER, RES_PRIORITY, RES_CONTROL, RES_WALKRGNS,
+ RES_BITMAP, RES_SAVE, RES_SEQUENCE,
+ // Return to Ringworld specific resource types
+ RT17, RT18, RT19, RT20, RT21, RT22, RT23, RT24, RT25, RT26, RT27, RT28, RT29, RT30, RT31
+};
+
+class SectionEntry {
+public:
+ ResourceType resType;
+ uint16 resNum;
+ uint32 fileOffset;
+
+ SectionEntry() {
+ resType = RES_LIBRARY;
+ resNum = 0;
+ fileOffset = 0;
+ }
+};
+
+class ResourceEntry {
+public:
+ uint16 id;
+ bool isCompressed;
+ uint32 fileOffset;
+ uint32 size;
+ uint32 uncompressedSize;
+
+ ResourceEntry() {
+ id = 0;
+ isCompressed = false;
+ fileOffset = 0;
+ size = 0;
+ uncompressedSize = 0;
+ }
+};
+
+typedef Common::List<ResourceEntry> ResourceList;
+
+class SectionList : public Common::List<SectionEntry> {
+public:
+ uint32 fileOffset;
+
+ SectionList() {
+ fileOffset = 0;
+ }
+};
+
+class BitReader {
+private:
+ Common::ReadStream &_stream;
+ uint8 _remainder, _bitsLeft;
+ byte readByte() { return _stream.eos() ? 0 : _stream.readByte(); }
+public:
+ BitReader(Common::ReadStream &s) : _stream(s) {
+ numBits = 9;
+ _remainder = 0;
+ _bitsLeft = 0;
+ }
+ uint16 readToken();
+
+ int numBits;
+};
+
+class TLib {
+private:
+ Common::StringArray _resStrings;
+private:
+ Common::File _file;
+ Common::String _filename;
+ ResourceList _resources;
+ SectionList _sections;
+
+ void loadSection(uint32 fileOffset);
+ void loadIndex();
+
+ static bool scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, ResourceEntry &resEntry);
+ static void loadSection(Common::File &f, ResourceList &resources);
+public:
+ TLib(const Common::String &filename);
+ ~TLib();
+
+ const Common::String &getFilename() { return _filename; }
+ const SectionList &getSections() { return _sections; }
+ Common::SeekableReadStream *getResource(uint16 id, bool suppressErrors = false);
+ Common::SeekableReadStream *getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors = false);
+ uint32 getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry);
+ void getPalette(byte palette[PALETTE_SIZE], int paletteNum);
+};
+
+} // end of namespace TsAGE
+} // end of namespace Scalpel
+} // end of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp
new file mode 100644
index 0000000000..328bf647d4
--- /dev/null
+++ b/engines/sherlock/scene.cpp
@@ -0,0 +1,1437 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/scene.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/screen.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_scene.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+
+namespace Sherlock {
+
+static const int FS_TRANS[8] = {
+ Scalpel::STOP_UP, Scalpel::STOP_UPRIGHT, Scalpel::STOP_RIGHT, Scalpel::STOP_DOWNRIGHT,
+ Scalpel::STOP_DOWN, Scalpel::STOP_DOWNLEFT, Scalpel::STOP_LEFT, Scalpel::STOP_UPLEFT
+};
+
+/*----------------------------------------------------------------*/
+
+BgFileHeader::BgFileHeader() {
+ _numStructs = -1;
+ _numImages = -1;
+ _numcAnimations = -1;
+ _descSize = -1;
+ _seqSize = -1;
+
+ // Serrated Scalpel
+ _fill = -1;
+
+ // Rose Tattoo
+ _scrollSize = -1;
+ _bytesWritten = -1;
+ _fadeStyle = -1;
+ Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0);
+}
+
+void BgFileHeader::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ _numStructs = s.readUint16LE();
+ _numImages = s.readUint16LE();
+ _numcAnimations = s.readUint16LE();
+ _descSize = s.readUint16LE();
+ _seqSize = s.readUint16LE();
+
+ if (isRoseTattoo) {
+ _scrollSize = s.readUint16LE();
+ _bytesWritten = s.readUint32LE();
+ _fadeStyle = s.readByte();
+ } else {
+ _fill = s.readUint16LE();
+
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+void BgFileHeaderInfo::load(Common::SeekableReadStream &s) {
+ _filesize = s.readUint32LE();
+ _maxFrames = s.readByte();
+
+ char buffer[9];
+ s.read(buffer, 9);
+ _filename = Common::String(buffer);
+}
+
+void BgFileHeaderInfo::load3DO(Common::SeekableReadStream &s) {
+ _filesize = s.readUint32BE();
+ _maxFrames = s.readByte();
+
+ char buffer[9];
+ s.read(buffer, 9);
+ _filename = Common::String(buffer);
+ s.skip(2); // only on 3DO!
+}
+
+/*----------------------------------------------------------------*/
+
+void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ if (isRoseTattoo) {
+ char buffer[41];
+ s.read(buffer, 41);
+ _dest = Common::String(buffer);
+ }
+
+ left = s.readSint16LE();
+ top = s.readSint16LE();
+ setWidth(s.readUint16LE());
+ setHeight(s.readUint16LE());
+
+ _image = isRoseTattoo ? s.readByte() : 0;
+ _scene = s.readSint16LE();
+
+ if (!isRoseTattoo)
+ _allow = s.readSint16LE();
+
+ _newPosition.x = s.readSint16LE();
+ _newPosition.y = s.readSint16LE();
+ _newPosition._facing = s.readUint16LE();
+
+ if (isRoseTattoo)
+ _allow = s.readSint16LE();
+}
+
+void Exit::load3DO(Common::SeekableReadStream &s) {
+ left = s.readSint16BE();
+ top = s.readSint16BE();
+ setWidth(s.readUint16BE());
+ setHeight(s.readUint16BE());
+
+ _image = 0;
+ _scene = s.readSint16BE();
+
+ _allow = s.readSint16BE();
+
+ _newPosition.x = s.readSint16BE();
+ _newPosition.y = s.readSint16BE();
+ _newPosition._facing = s.readUint16BE();
+ s.skip(2); // Filler
+}
+
+/*----------------------------------------------------------------*/
+
+void SceneEntry::load(Common::SeekableReadStream &s) {
+ _startPosition.x = s.readSint16LE();
+ _startPosition.y = s.readSint16LE();
+ _startDir = s.readByte();
+ _allow = s.readByte();
+}
+
+void SceneEntry::load3DO(Common::SeekableReadStream &s) {
+ _startPosition.x = s.readSint16BE();
+ _startPosition.y = s.readSint16BE();
+ _startDir = s.readByte();
+ _allow = s.readByte();
+}
+
+void SceneSound::load(Common::SeekableReadStream &s) {
+ char buffer[9];
+ s.read(buffer, 8);
+ buffer[8] = '\0';
+
+ _name = Common::String(buffer);
+ _priority = s.readByte();
+}
+
+void SceneSound::load3DO(Common::SeekableReadStream &s) {
+ load(s);
+}
+
+/*----------------------------------------------------------------*/
+
+int ObjectArray::indexOf(const Object &obj) const {
+ for (uint idx = 0; idx < size(); ++idx) {
+ if (&(*this)[idx] == &obj)
+ return idx;
+ }
+
+ return -1;
+}
+
+/*----------------------------------------------------------------*/
+
+void ScaleZone::load(Common::SeekableReadStream &s) {
+ left = s.readSint16LE();
+ top = s.readSint16LE();
+ setWidth(s.readUint16LE());
+ setHeight(s.readUint16LE());
+
+ _topNumber = s.readByte();
+ _bottomNumber = s.readByte();
+}
+
+/*----------------------------------------------------------------*/
+
+void WalkArray::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ _pointsCount = (int8)s.readByte();
+
+ for (int idx = 0; idx < _pointsCount; ++idx) {
+ int x = s.readSint16LE();
+ int y = isRoseTattoo ? s.readSint16LE() : s.readByte();
+ push_back(Common::Point(x, y));
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+Scene *Scene::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelScene(vm);
+ else
+ return new Tattoo::TattooScene(vm);
+}
+
+Scene::Scene(SherlockEngine *vm): _vm(vm) {
+ _sceneStats = new bool *[SCENES_COUNT];
+ _sceneStats[0] = new bool[SCENES_COUNT * 65];
+ Common::fill(&_sceneStats[0][0], &_sceneStats[0][SCENES_COUNT * 65], false);
+ for (int idx = 1; idx < SCENES_COUNT; ++idx) {
+ _sceneStats[idx] = _sceneStats[idx - 1] + 65;
+ }
+ _currentScene = -1;
+ _goToScene = -1;
+ _loadingSavedGame = false;
+ _walkedInScene = false;
+ _version = 0;
+ _compressed = false;
+ _invGraphicItems = 0;
+ _cAnimFramePause = 0;
+ _restoreFlag = false;
+ _animating = 0;
+ _doBgAnimDone = true;
+ _tempFadeStyle = 0;
+ _doBgAnimDone = false;
+}
+
+Scene::~Scene() {
+ freeScene();
+ delete[] _sceneStats[0];
+ delete[] _sceneStats;
+}
+
+void Scene::selectScene() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ UserInterface &ui = *_vm->_ui;
+
+ // Reset fields
+ ui._windowOpen = ui._infoFlag = false;
+ ui._menuMode = STD_MODE;
+
+ // Free any previous scene
+ freeScene();
+
+ // Load the scene
+ Common::String sceneFile = Common::String::format("res%02d", _goToScene);
+ // _rrmName gets set during loadScene()
+ // _rrmName is for ScalpelScene::startCAnim
+ _currentScene = _goToScene;
+ _goToScene = -1;
+
+ loadScene(sceneFile);
+
+ // If the fade style was changed from running a movie, then reset it
+ if (_tempFadeStyle) {
+ screen._fadeStyle = _tempFadeStyle;
+ _tempFadeStyle = 0;
+ }
+
+ people[HOLMES]._walkDest = Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER);
+
+ _restoreFlag = true;
+ events.clearEvents();
+
+ // If there were any scripts waiting to be run, but were interrupt by a running
+ // canimation (probably the last scene's exit canim), clear the _scriptMoreFlag
+ if (talk._scriptMoreFlag == 3)
+ talk._scriptMoreFlag = 0;
+}
+
+void Scene::freeScene() {
+ if (_currentScene == -1)
+ return;
+
+ _vm->_ui->clearWindow();
+ _vm->_talk->freeTalkVars();
+ _vm->_inventory->freeInv();
+ _vm->_music->freeSong();
+ _vm->_sound->freeLoadedSounds();
+
+ if (!_loadingSavedGame)
+ saveSceneStatus();
+ else
+ _loadingSavedGame = false;
+
+ _sequenceBuffer.clear();
+ _descText.clear();
+ _walkPoints.clear();
+ _cAnim.clear();
+ _bgShapes.clear();
+ _zones.clear();
+ _canimShapes.clear();
+
+ for (uint idx = 0; idx < _images.size(); ++idx)
+ delete _images[idx]._images;
+ _images.clear();
+
+ _currentScene = -1;
+}
+
+bool Scene::loadScene(const Common::String &filename) {
+ Events &events = *_vm->_events;
+ Music &music = *_vm->_music;
+ People &people = *_vm->_people;
+ Resources &res = *_vm->_res;
+ SaveManager &saves = *_vm->_saves;
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+ bool flag;
+
+ _walkedInScene = false;
+
+ // Reset the list of walkable areas
+ _zones.clear();
+ _zones.push_back(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ _descText.clear();
+ _comments = "";
+ _bgShapes.clear();
+ _cAnim.clear();
+ _sequenceBuffer.clear();
+
+ //
+ // Load the room resource file for the scene
+ //
+
+ if (!IS_3DO) {
+ // PC version
+ Common::String roomFilename = filename + ".rrm";
+ _roomFilename = roomFilename;
+
+ flag = _vm->_res->exists(roomFilename);
+ if (flag) {
+ Common::SeekableReadStream *rrmStream = _vm->_res->load(roomFilename);
+
+ rrmStream->seek(39);
+ if (IS_SERRATED_SCALPEL) {
+ _version = rrmStream->readByte();
+ _compressed = _version == 10;
+ } else {
+ _compressed = rrmStream->readByte() > 0;
+ }
+
+ // Go to header and read it in
+ rrmStream->seek(rrmStream->readUint32LE());
+
+ BgFileHeader bgHeader;
+ bgHeader.load(*rrmStream, IS_ROSE_TATTOO);
+ _invGraphicItems = bgHeader._numImages + 1;
+
+ if (IS_ROSE_TATTOO) {
+ // Resize the screen if necessary
+ int fullWidth = SHERLOCK_SCREEN_WIDTH + bgHeader._scrollSize;
+ if (screen._backBuffer1.w() != fullWidth) {
+ screen._backBuffer1.create(fullWidth, SHERLOCK_SCREEN_HEIGHT);
+ screen._backBuffer2.create(fullWidth, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ // Handle initializing the palette
+ screen.initPaletteFade(bgHeader._bytesWritten);
+ rrmStream->read(screen._cMap, PALETTE_SIZE);
+ screen.translatePalette(screen._cMap);
+
+ paletteLoaded();
+
+ // Read in background
+ if (_compressed) {
+ res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ rrmStream->read(screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT);
+ }
+ }
+
+ // Read in the shapes header info
+ Common::Array<BgFileHeaderInfo> bgInfo;
+ bgInfo.resize(bgHeader._numStructs);
+
+ for (uint idx = 0; idx < bgInfo.size(); ++idx)
+ bgInfo[idx].load(*rrmStream);
+
+ // Read information
+ if (IS_ROSE_TATTOO) {
+ // Load shapes
+ Common::SeekableReadStream *infoStream = !_compressed ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625);
+
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*infoStream, true);
+
+ if (_compressed)
+ delete infoStream;
+
+ // Load description text
+ _descText.resize(bgHeader._descSize);
+ if (_compressed)
+ res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize);
+ else
+ rrmStream->read(&_descText[0], bgHeader._descSize);
+
+ // Load sequences
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ if (_compressed)
+ res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize);
+ else
+ rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+ } else if (!_compressed) {
+ // Serrated Scalpel uncompressed info
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*rrmStream, false);
+
+ if (bgHeader._descSize) {
+ _descText.resize(bgHeader._descSize);
+ rrmStream->read(&_descText[0], bgHeader._descSize);
+ }
+
+ if (bgHeader._seqSize) {
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+ }
+ } else {
+ // Serrated Scalpel compressed info
+ Common::SeekableReadStream *infoStream;
+
+ // Read shapes
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569);
+
+ _bgShapes.resize(bgHeader._numStructs);
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx)
+ _bgShapes[idx].load(*infoStream, false);
+
+ delete infoStream;
+
+ // Read description texts
+ if (bgHeader._descSize) {
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize);
+
+ _descText.resize(bgHeader._descSize);
+ infoStream->read(&_descText[0], bgHeader._descSize);
+
+ delete infoStream;
+ }
+
+ // Read sequences
+ if (bgHeader._seqSize) {
+ infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize);
+
+ _sequenceBuffer.resize(bgHeader._seqSize);
+ infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize);
+
+ delete infoStream;
+ }
+ }
+
+ // Set up the list of images used by the scene
+ _images.resize(bgHeader._numImages + 1);
+ for (int idx = 0; idx < bgHeader._numImages; ++idx) {
+ _images[idx + 1]._filesize = bgInfo[idx]._filesize;
+ _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames;
+
+ // Read in the image data
+ Common::SeekableReadStream *imageStream = _compressed ?
+ res.decompress(*rrmStream, bgInfo[idx]._filesize) :
+ rrmStream->readStream(bgInfo[idx]._filesize);
+
+ _images[idx + 1]._images = new ImageFile(*imageStream);
+
+ delete imageStream;
+ }
+
+ // Set up the bgShapes
+ for (int idx = 0; idx < bgHeader._numStructs; ++idx) {
+ _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images;
+ _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr :
+ &(*_bgShapes[idx]._images)[0];
+
+ _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]);
+ _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset];
+ _bgShapes[idx]._misc = 0;
+ _bgShapes[idx]._seqCounter = 0;
+ _bgShapes[idx]._seqCounter2 = 0;
+ _bgShapes[idx]._seqStack = 0;
+ _bgShapes[idx]._frameNumber = -1;
+ _bgShapes[idx]._oldPosition = Common::Point(0, 0);
+ _bgShapes[idx]._oldSize = Common::Point(1, 1);
+ }
+
+ // Load in cAnim list
+ _cAnim.clear();
+ if (bgHeader._numcAnimations) {
+ int animSize = IS_SERRATED_SCALPEL ? 65 : 47;
+ Common::SeekableReadStream *cAnimStream = _compressed ?
+ res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) :
+ rrmStream->readStream(animSize * bgHeader._numcAnimations);
+
+ // Load cAnim offset table as well
+ uint32 *cAnimOffsetTablePtr = new uint32[bgHeader._numcAnimations];
+ uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr;
+ memset(cAnimOffsetTablePtr, 0, bgHeader._numcAnimations * sizeof(uint32));
+ if (IS_SERRATED_SCALPEL) {
+ // Save current stream offset
+ int32 curOffset = rrmStream->pos();
+ rrmStream->seek(44); // Seek to cAnim-Offset-Table
+ for (uint16 curCAnim = 0; curCAnim < bgHeader._numcAnimations; curCAnim++) {
+ *cAnimOffsetPtr = rrmStream->readUint32LE();
+ cAnimOffsetPtr++;
+ }
+ // Seek back to original stream offset
+ rrmStream->seek(curOffset);
+ }
+ // TODO: load offset table for Rose Tattoo as well
+
+ // Go to the start of the cAnimOffsetTable
+ cAnimOffsetPtr = cAnimOffsetTablePtr;
+
+ _cAnim.resize(bgHeader._numcAnimations);
+ for (uint idx = 0; idx < _cAnim.size(); ++idx) {
+ _cAnim[idx].load(*cAnimStream, IS_ROSE_TATTOO, *cAnimOffsetPtr);
+ cAnimOffsetPtr++;
+ }
+
+ delete cAnimStream;
+ delete[] cAnimOffsetTablePtr;
+ }
+
+
+
+ // Read in the room bounding areas
+ int size = rrmStream->readUint16LE();
+ Common::SeekableReadStream *boundsStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, size);
+
+ _zones.resize(size / 10);
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ _zones[idx].left = boundsStream->readSint16LE();
+ _zones[idx].top = boundsStream->readSint16LE();
+ _zones[idx].setWidth(boundsStream->readSint16LE() + 1);
+ _zones[idx].setHeight(boundsStream->readSint16LE() + 1);
+ boundsStream->skip(2); // Skip unused scene number field
+ }
+
+ if (_compressed)
+ delete boundsStream;
+
+ // Ensure we've reached the path version byte
+ if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251))
+ error("Invalid scene path data");
+
+ // Load the walk directory and walk data
+ assert(_zones.size() < MAX_ZONES);
+
+
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ Common::fill(&_walkDirectory[idx1][0], &_walkDirectory[idx1][MAX_ZONES], 0);
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2)
+ _walkDirectory[idx1][idx2] = rrmStream->readSint16LE();
+ }
+
+ // Read in the walk data
+ size = rrmStream->readUint16LE();
+ Common::SeekableReadStream *walkStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, size);
+
+ int startPos = walkStream->pos();
+ while ((walkStream->pos() - startPos) < size) {
+ _walkPoints.push_back(WalkArray());
+ _walkPoints[_walkPoints.size() - 1]._fileOffset = walkStream->pos() - startPos;
+ _walkPoints[_walkPoints.size() - 1].load(*walkStream, IS_ROSE_TATTOO);
+ }
+
+ if (_compressed)
+ delete walkStream;
+
+ // Translate the file offsets of the walk directory to indexes in the loaded walk data
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) {
+ int fileOffset = _walkDirectory[idx1][idx2];
+ if (fileOffset == -1)
+ continue;
+
+ uint dataIndex = 0;
+ while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset)
+ ++dataIndex;
+ assert(dataIndex < _walkPoints.size());
+ _walkDirectory[idx1][idx2] = dataIndex;
+ }
+ }
+
+ if (IS_ROSE_TATTOO) {
+ // Read in the entrance
+ _entrance.load(*rrmStream);
+
+ // Load scale zones
+ _scaleZones.resize(rrmStream->readByte());
+ for (uint idx = 0; idx < _scaleZones.size(); ++idx)
+ _scaleZones[idx].load(*rrmStream);
+ }
+
+ // Read in the exits
+ int numExits = rrmStream->readByte();
+ _exits.resize(numExits);
+
+ for (int idx = 0; idx < numExits; ++idx)
+ _exits[idx].load(*rrmStream, IS_ROSE_TATTOO);
+
+ if (IS_SERRATED_SCALPEL)
+ // Read in the entrance
+ _entrance.load(*rrmStream);
+
+ // Initialize sound list
+ int numSounds = rrmStream->readByte();
+ _sounds.resize(numSounds);
+
+ for (int idx = 0; idx < numSounds; ++idx)
+ _sounds[idx].load(*rrmStream);
+
+ loadSceneSounds();
+
+ if (IS_ROSE_TATTOO) {
+ // Load the object sound list
+ char buffer[27];
+
+ _objSoundList.resize(rrmStream->readUint16LE());
+ for (uint idx = 0; idx < _objSoundList.size(); ++idx) {
+ rrmStream->read(buffer, 27);
+ _objSoundList[idx] = Common::String(buffer);
+ }
+ } else {
+ // Read in palette
+ rrmStream->read(screen._cMap, PALETTE_SIZE);
+ screen.translatePalette(screen._cMap);
+ Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap);
+
+ // Read in the background
+ Common::SeekableReadStream *bgStream = !_compressed ? rrmStream :
+ res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+
+ bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT);
+
+ if (_compressed)
+ delete bgStream;
+ }
+
+ // Backup the image and set the palette
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+ screen.setPalette(screen._cMap);
+
+ delete rrmStream;
+ }
+
+ } else {
+ // === 3DO version ===
+ _roomFilename = "rooms/" + filename + ".rrm";
+ flag = _vm->_res->exists(_roomFilename);
+ if (!flag)
+ error("loadScene: 3DO room data file not found");
+
+ Common::SeekableReadStream *roomStream = _vm->_res->load(_roomFilename);
+ uint32 roomStreamSize = roomStream->size();
+
+ // there should be at least all bytes of the header data
+ if (roomStreamSize < 128)
+ error("loadScene: 3DO room data file is too small");
+
+ // Read 3DO header
+ roomStream->skip(4); // UINT32: offset graphic data?
+ uint16 header3DO_numStructs = roomStream->readUint16BE();
+ uint16 header3DO_numImages = roomStream->readUint16BE();
+ uint16 header3DO_numAnimations = roomStream->readUint16BE();
+ roomStream->skip(6);
+
+ uint32 header3DO_bgInfo_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgInfo_size = roomStream->readUint32BE();
+ uint32 header3DO_bgShapes_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgShapes_size = roomStream->readUint32BE();
+ uint32 header3DO_descriptions_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_descriptions_size = roomStream->readUint32BE();
+ uint32 header3DO_sequence_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_sequence_size = roomStream->readUint32BE();
+ uint32 header3DO_cAnim_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_cAnim_size = roomStream->readUint32BE();
+ uint32 header3DO_roomBounding_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_roomBounding_size = roomStream->readUint32BE();
+ uint32 header3DO_walkDirectory_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_walkDirectory_size = roomStream->readUint32BE();
+ uint32 header3DO_walkData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_walkData_size = roomStream->readUint32BE();
+ uint32 header3DO_exits_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_exits_size = roomStream->readUint32BE();
+ uint32 header3DO_entranceData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_entranceData_size = roomStream->readUint32BE();
+ uint32 header3DO_soundList_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_soundList_size = roomStream->readUint32BE();
+ //uint32 header3DO_unknown_offset = roomStream->readUint32BE() + 0x80;
+ //uint32 header3DO_unknown_size = roomStream->readUint32BE();
+ roomStream->skip(8); // Skip over unknown offset+size
+ uint32 header3DO_bgGraphicData_offset = roomStream->readUint32BE() + 0x80;
+ uint32 header3DO_bgGraphicData_size = roomStream->readUint32BE();
+
+ // Calculate amount of entries
+ int32 header3DO_soundList_count = header3DO_soundList_size / 9;
+
+ _invGraphicItems = header3DO_numImages + 1;
+
+ // Verify all offsets
+ if (header3DO_bgInfo_offset >= roomStreamSize)
+ error("loadScene: 3DO bgInfo offset points outside of room file");
+ if (header3DO_bgInfo_size > (roomStreamSize - header3DO_bgInfo_offset))
+ error("loadScene: 3DO bgInfo size goes beyond room file");
+ if (header3DO_bgShapes_offset >= roomStreamSize)
+ error("loadScene: 3DO bgShapes offset points outside of room file");
+ if (header3DO_bgShapes_size > (roomStreamSize - header3DO_bgShapes_offset))
+ error("loadScene: 3DO bgShapes size goes beyond room file");
+ if (header3DO_descriptions_offset >= roomStreamSize)
+ error("loadScene: 3DO descriptions offset points outside of room file");
+ if (header3DO_descriptions_size > (roomStreamSize - header3DO_descriptions_offset))
+ error("loadScene: 3DO descriptions size goes beyond room file");
+ if (header3DO_sequence_offset >= roomStreamSize)
+ error("loadScene: 3DO sequence offset points outside of room file");
+ if (header3DO_sequence_size > (roomStreamSize - header3DO_sequence_offset))
+ error("loadScene: 3DO sequence size goes beyond room file");
+ if (header3DO_cAnim_offset >= roomStreamSize)
+ error("loadScene: 3DO cAnim offset points outside of room file");
+ if (header3DO_cAnim_size > (roomStreamSize - header3DO_cAnim_offset))
+ error("loadScene: 3DO cAnim size goes beyond room file");
+ if (header3DO_roomBounding_offset >= roomStreamSize)
+ error("loadScene: 3DO roomBounding offset points outside of room file");
+ if (header3DO_roomBounding_size > (roomStreamSize - header3DO_roomBounding_offset))
+ error("loadScene: 3DO roomBounding size goes beyond room file");
+ if (header3DO_walkDirectory_offset >= roomStreamSize)
+ error("loadScene: 3DO walkDirectory offset points outside of room file");
+ if (header3DO_walkDirectory_size > (roomStreamSize - header3DO_walkDirectory_offset))
+ error("loadScene: 3DO walkDirectory size goes beyond room file");
+ if (header3DO_walkData_offset >= roomStreamSize)
+ error("loadScene: 3DO walkData offset points outside of room file");
+ if (header3DO_walkData_size > (roomStreamSize - header3DO_walkData_offset))
+ error("loadScene: 3DO walkData size goes beyond room file");
+ if (header3DO_exits_offset >= roomStreamSize)
+ error("loadScene: 3DO exits offset points outside of room file");
+ if (header3DO_exits_size > (roomStreamSize - header3DO_exits_offset))
+ error("loadScene: 3DO exits size goes beyond room file");
+ if (header3DO_entranceData_offset >= roomStreamSize)
+ error("loadScene: 3DO entranceData offset points outside of room file");
+ if (header3DO_entranceData_size > (roomStreamSize - header3DO_entranceData_offset))
+ error("loadScene: 3DO entranceData size goes beyond room file");
+ if (header3DO_soundList_offset >= roomStreamSize)
+ error("loadScene: 3DO soundList offset points outside of room file");
+ if (header3DO_soundList_size > (roomStreamSize - header3DO_soundList_offset))
+ error("loadScene: 3DO soundList size goes beyond room file");
+ if (header3DO_bgGraphicData_offset >= roomStreamSize)
+ error("loadScene: 3DO bgGraphicData offset points outside of room file");
+ if (header3DO_bgGraphicData_size > (roomStreamSize - header3DO_bgGraphicData_offset))
+ error("loadScene: 3DO bgGraphicData size goes beyond room file");
+
+ // === BGINFO === read in the shapes header info
+ Common::Array<BgFileHeaderInfo> bgInfo;
+
+ uint32 expected3DO_bgInfo_size = header3DO_numStructs * 16;
+ if (expected3DO_bgInfo_size != header3DO_bgInfo_size) // Security check
+ error("loadScene: 3DO bgInfo size mismatch");
+
+ roomStream->seek(header3DO_bgInfo_offset);
+ bgInfo.resize(header3DO_numStructs);
+ for (uint idx = 0; idx < bgInfo.size(); ++idx)
+ bgInfo[idx].load3DO(*roomStream);
+
+ // === BGSHAPES === read in the shapes info
+ uint32 expected3DO_bgShapes_size = header3DO_numStructs * 588;
+ if (expected3DO_bgShapes_size != header3DO_bgShapes_size) // Security check
+ error("loadScene: 3DO bgShapes size mismatch");
+
+ roomStream->seek(header3DO_bgShapes_offset);
+ _bgShapes.resize(header3DO_numStructs);
+ for (int idx = 0; idx < header3DO_numStructs; ++idx)
+ _bgShapes[idx].load3DO(*roomStream);
+
+ // === DESCRIPTION === read description text
+ if (header3DO_descriptions_size) {
+ roomStream->seek(header3DO_descriptions_offset);
+ _descText.resize(header3DO_descriptions_size);
+ roomStream->read(&_descText[0], header3DO_descriptions_size);
+ }
+
+ // === SEQUENCE === read sequence buffer
+ if (header3DO_sequence_size) {
+ roomStream->seek(header3DO_sequence_offset);
+ _sequenceBuffer.resize(header3DO_sequence_size);
+ roomStream->read(&_sequenceBuffer[0], header3DO_sequence_size);
+ }
+
+ // === IMAGES === set up the list of images used by the scene
+ roomStream->seek(header3DO_bgGraphicData_offset);
+ _images.resize(header3DO_numImages + 1);
+ for (int idx = 0; idx < header3DO_numImages; ++idx) {
+ _images[idx + 1]._filesize = bgInfo[idx]._filesize;
+ _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames;
+
+ // Read image data into memory
+ Common::SeekableReadStream *imageStream = roomStream->readStream(bgInfo[idx]._filesize);
+
+ // Load image data into an ImageFile array as room file data
+ // which is basically a fixed header, followed by a raw cel header, followed by regular cel data
+ _images[idx + 1]._images = new ImageFile3DO(*imageStream, true);
+
+ delete imageStream;
+ }
+
+ // === BGSHAPES === Set up the bgShapes
+ for (int idx = 0; idx < header3DO_numStructs; ++idx) {
+ _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images;
+ _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr :
+ &(*_bgShapes[idx]._images)[0];
+
+ _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]);
+ _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset];
+ _bgShapes[idx]._misc = 0;
+ _bgShapes[idx]._seqCounter = 0;
+ _bgShapes[idx]._seqCounter2 = 0;
+ _bgShapes[idx]._seqStack = 0;
+ _bgShapes[idx]._frameNumber = -1;
+ _bgShapes[idx]._oldPosition = Common::Point(0, 0);
+ _bgShapes[idx]._oldSize = Common::Point(1, 1);
+ }
+
+ // === CANIM === read cAnim list
+ _cAnim.clear();
+ if (header3DO_numAnimations) {
+ roomStream->seek(header3DO_cAnim_offset);
+ Common::SeekableReadStream *cAnimStream = roomStream->readStream(header3DO_cAnim_size);
+
+ uint32 *cAnimOffsetTablePtr = new uint32[header3DO_numAnimations];
+ uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr;
+ uint32 cAnimOffset = 0;
+ memset(cAnimOffsetTablePtr, 0, header3DO_numAnimations * sizeof(uint32));
+
+ // Seek to end of graphics data and load cAnim offset table from there
+ roomStream->seek(header3DO_bgGraphicData_offset + header3DO_bgGraphicData_size);
+ for (uint16 curCAnim = 0; curCAnim < header3DO_numAnimations; curCAnim++) {
+ cAnimOffset = roomStream->readUint32BE();
+ if (cAnimOffset >= roomStreamSize)
+ error("loadScene: 3DO cAnim entry offset points outside of room file");
+
+ *cAnimOffsetPtr = cAnimOffset;
+ cAnimOffsetPtr++;
+ }
+
+ // Go to the start of the cAnimOffsetTable
+ cAnimOffsetPtr = cAnimOffsetTablePtr;
+
+ _cAnim.resize(header3DO_numAnimations);
+ for (uint idx = 0; idx < _cAnim.size(); ++idx) {
+ _cAnim[idx].load3DO(*cAnimStream, *cAnimOffsetPtr);
+ cAnimOffsetPtr++;
+ }
+
+ delete cAnimStream;
+ delete[] cAnimOffsetTablePtr;
+ }
+
+ // === BOUNDING AREAS === Read in the room bounding areas
+ int roomBoundingCount = header3DO_roomBounding_size / 12;
+ uint32 expected3DO_roomBounding_size = roomBoundingCount * 12;
+ if (expected3DO_roomBounding_size != header3DO_roomBounding_size)
+ error("loadScene: 3DO roomBounding size mismatch");
+
+ roomStream->seek(header3DO_roomBounding_offset);
+ _zones.resize(roomBoundingCount);
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ _zones[idx].left = roomStream->readSint16BE();
+ _zones[idx].top = roomStream->readSint16BE();
+ _zones[idx].setWidth(roomStream->readSint16BE() + 1);
+ _zones[idx].setHeight(roomStream->readSint16BE() + 1);
+ roomStream->skip(4); // skip UINT32
+ }
+
+ // === WALK DIRECTORY === Load the walk directory
+ uint32 expected3DO_walkDirectory_size = _zones.size() * _zones.size() * 2;
+ if (expected3DO_walkDirectory_size != header3DO_walkDirectory_size)
+ error("loadScene: 3DO walkDirectory size mismatch");
+
+ roomStream->seek(header3DO_walkDirectory_offset);
+ assert(_zones.size() < MAX_ZONES);
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2)
+ _walkDirectory[idx1][idx2] = roomStream->readSint16BE();
+ }
+
+ // === WALK DATA === Read in the walk data
+ roomStream->seek(header3DO_walkData_offset);
+
+ int startPos = roomStream->pos();
+ while ((roomStream->pos() - startPos) < (int)header3DO_walkData_size) {
+ _walkPoints.push_back(WalkArray());
+ _walkPoints[_walkPoints.size() - 1]._fileOffset = roomStream->pos() - startPos;
+ _walkPoints[_walkPoints.size() - 1].load(*roomStream, false);
+ }
+
+ // Translate the file offsets of the walk directory to indexes in the loaded walk data
+ for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) {
+ for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) {
+ int fileOffset = _walkDirectory[idx1][idx2];
+ if (fileOffset == -1)
+ continue;
+
+ uint dataIndex = 0;
+ while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset)
+ ++dataIndex;
+ assert(dataIndex < _walkPoints.size());
+ _walkDirectory[idx1][idx2] = dataIndex;
+ }
+ }
+
+ // === EXITS === Read in the exits
+ roomStream->seek(header3DO_exits_offset);
+
+ int exitsCount = header3DO_exits_size / 20;
+
+ _exits.resize(exitsCount);
+ for (int idx = 0; idx < exitsCount; ++idx)
+ _exits[idx].load3DO(*roomStream);
+
+ // === ENTRANCE === Read in the entrance
+ if (header3DO_entranceData_size != 8)
+ error("loadScene: 3DO entranceData size mismatch");
+
+ roomStream->seek(header3DO_entranceData_offset);
+ _entrance.load3DO(*roomStream);
+
+ // === SOUND LIST === Initialize sound list
+ roomStream->seek(header3DO_soundList_offset);
+ _sounds.resize(header3DO_soundList_count);
+ for (int idx = 0; idx < header3DO_soundList_count; ++idx)
+ _sounds[idx].load3DO(*roomStream);
+
+ delete roomStream;
+
+ // === BACKGROUND PICTURE ===
+ // load from file rooms\[filename].bg
+ // it's uncompressed 15-bit RGB555 data
+
+ Common::String roomBackgroundFilename = "rooms/" + filename + ".bg";
+ flag = _vm->_res->exists(roomBackgroundFilename);
+ if (!flag)
+ error("loadScene: 3DO room background file not found (%s)", roomBackgroundFilename.c_str());
+
+ Common::File roomBackgroundStream;
+ if (!roomBackgroundStream.open(roomBackgroundFilename))
+ error("Could not open file - %s", roomBackgroundFilename.c_str());
+
+ int totalPixelCount = SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT;
+ uint16 *roomBackgroundDataPtr = NULL;
+ uint16 *pixelSourcePtr = NULL;
+ uint16 *pixelDestPtr = (uint16 *)screen._backBuffer1.getPixels();
+ uint16 curPixel = 0;
+ uint32 roomBackgroundStreamSize = roomBackgroundStream.size();
+ uint32 expectedBackgroundSize = totalPixelCount * 2;
+
+ // Verify file size of background file
+ if (expectedBackgroundSize != roomBackgroundStreamSize)
+ error("loadScene: 3DO room background file not expected size");
+
+ roomBackgroundDataPtr = new uint16[totalPixelCount];
+ roomBackgroundStream.read(roomBackgroundDataPtr, roomBackgroundStreamSize);
+ roomBackgroundStream.close();
+
+ // Convert data from RGB555 to RGB565
+ pixelSourcePtr = roomBackgroundDataPtr;
+ for (int pixels = 0; pixels < totalPixelCount; pixels++) {
+ curPixel = READ_BE_UINT16(pixelSourcePtr++);
+
+ byte curPixelRed = (curPixel >> 10) & 0x1F;
+ byte curPixelGreen = (curPixel >> 5) & 0x1F;
+ byte curPixelBlue = curPixel & 0x1F;
+ *pixelDestPtr = ((curPixelRed << 11) | (curPixelGreen << 6) | (curPixelBlue));
+ pixelDestPtr++;
+ }
+
+ delete[] roomBackgroundDataPtr;
+
+#if 0
+ // code to show the background
+ screen.blitFrom(screen._backBuffer1);
+ _vm->_events->wait(10000);
+#endif
+
+ // Backup the image
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+ }
+
+ // Handle drawing any on-screen interface
+ ui.drawInterface();
+
+ checkSceneStatus();
+
+ if (!saves._justLoaded) {
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == HIDDEN && _bgShapes[idx]._aType == TALK_EVERY)
+ _bgShapes[idx].toggleHidden();
+ }
+
+ // Check for TURNON objects
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == HIDDEN && (_bgShapes[idx]._flags & TURNON_OBJ))
+ _bgShapes[idx].toggleHidden();
+ }
+
+ // Check for TURNOFF objects
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type != HIDDEN && (_bgShapes[idx]._flags & TURNOFF_OBJ) &&
+ _bgShapes[idx]._type != INVALID)
+ _bgShapes[idx].toggleHidden();
+ if (_bgShapes[idx]._type == HIDE_SHAPE)
+ // Hiding isn't needed, since objects aren't drawn yet
+ _bgShapes[idx]._type = HIDDEN;
+ }
+ }
+
+ checkSceneFlags(false);
+ checkInventory();
+
+ // Handle starting any music for the scene
+ if (IS_SERRATED_SCALPEL && music._musicOn && music.loadSong(_currentScene))
+ music.startSong();
+
+ // Load walking images if not already loaded
+ people.loadWalk();
+
+ // Transition to the scene and setup entrance co-ordinates and animations
+ transitionToScene();
+
+ // Player has not yet walked in this scene
+ _walkedInScene = false;
+ saves._justLoaded = false;
+
+ events.clearEvents();
+ return flag;
+}
+
+void Scene::loadSceneSounds() {
+ Sound &sound = *_vm->_sound;
+
+ for (uint idx = 0; idx < _sounds.size(); ++idx)
+ sound.loadSound(_sounds[idx]._name, _sounds[idx]._priority);
+}
+
+void Scene::checkSceneStatus() {
+ if (_sceneStats[_currentScene][64]) {
+ for (uint idx = 0; idx < 64; ++idx) {
+ bool flag = _sceneStats[_currentScene][idx];
+
+ if (idx < _bgShapes.size()) {
+ Object &obj = _bgShapes[idx];
+
+ if (flag) {
+ // No shape to erase, so flag as hidden
+ obj._type = HIDDEN;
+ } else if (obj._images == nullptr || obj._images->size() == 0) {
+ // No shape
+ obj._type = NO_SHAPE;
+ } else {
+ obj._type = ACTIVE_BG_SHAPE;
+ }
+ } else {
+ // Finished checks
+ return;
+ }
+ }
+ }
+}
+
+void Scene::saveSceneStatus() {
+ // Flag any objects for the scene that have been altered
+ int count = MIN((int)_bgShapes.size(), 64);
+ for (int idx = 0; idx < count; ++idx) {
+ Object &obj = _bgShapes[idx];
+ _sceneStats[_currentScene][idx] = obj._type == HIDDEN || obj._type == REMOVE
+ || obj._type == HIDE_SHAPE || obj._type == INVALID;
+ }
+
+ // Flag scene as having been visited
+ _sceneStats[_currentScene][64] = true;
+}
+
+void Scene::checkSceneFlags(bool flag) {
+ SpriteType mode = flag ? HIDE_SHAPE : HIDDEN;
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ bool objectFlag = true;
+
+ if (o._requiredFlag[0] || o._requiredFlag[1]) {
+ if (o._requiredFlag[0] != 0)
+ objectFlag = _vm->readFlags(o._requiredFlag[0]);
+ if (o._requiredFlag[1] != 0)
+ objectFlag &= _vm->readFlags(o._requiredFlag[1]);
+
+ if (!objectFlag) {
+ // Kill object
+ if (o._type != HIDDEN && o._type != INVALID) {
+ if (o._images == nullptr || o._images->size() == 0)
+ // No shape to erase, so flag as hidden
+ o._type = HIDDEN;
+ else
+ // Flag it as needing to be hidden after first erasing it
+ o._type = mode;
+ }
+ } else if (IS_ROSE_TATTOO || o._requiredFlag[0] > 0) {
+ // Restore object
+ if (o._images == nullptr || o._images->size() == 0)
+ o._type = NO_SHAPE;
+ else
+ o._type = ACTIVE_BG_SHAPE;
+ }
+ }
+ }
+
+ // Check inventory for items to remove based on flag changes
+ for (int idx = 0; idx < _vm->_inventory->_holdings; ++idx) {
+ InventoryItem &ii = (*_vm->_inventory)[idx];
+ if (ii._requiredFlag && !_vm->readFlags(ii._requiredFlag)) {
+ // Kill object: move it after the active holdings
+ InventoryItem tempItem = (*_vm->_inventory)[idx];
+ _vm->_inventory->insert_at(_vm->_inventory->_holdings, tempItem);
+ _vm->_inventory->remove_at(idx);
+ _vm->_inventory->_holdings--;
+ }
+ }
+
+ // Check inactive inventory items for ones to reactivate based on flag changes
+ for (uint idx = _vm->_inventory->_holdings; idx < _vm->_inventory->size(); ++idx) {
+ InventoryItem &ii = (*_vm->_inventory)[idx];
+ if (ii._requiredFlag && _vm->readFlags(ii._requiredFlag)) {
+ // Restore object: move it after the active holdings
+ InventoryItem tempItem = (*_vm->_inventory)[idx];
+ _vm->_inventory->remove_at(idx);
+ _vm->_inventory->insert_at(_vm->_inventory->_holdings, tempItem);
+ _vm->_inventory->_holdings++;
+ }
+ }
+}
+
+void Scene::checkInventory() {
+ for (uint shapeIdx = 0; shapeIdx < _bgShapes.size(); ++shapeIdx) {
+ for (int invIdx = 0; invIdx < _vm->_inventory->_holdings; ++invIdx) {
+ if (_bgShapes[shapeIdx]._name.equalsIgnoreCase((*_vm->_inventory)[invIdx]._name)) {
+ _bgShapes[shapeIdx]._type = INVALID;
+ break;
+ }
+ }
+ }
+}
+
+void Scene::transitionToScene() {
+ People &people = *_vm->_people;
+ SaveManager &saves = *_vm->_saves;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Point32 &hSavedPos = people._savedPos;
+ int &hSavedFacing = people._savedPos._facing;
+
+ if (hSavedPos.x < 1) {
+ // No exit information from last scene-check entrance info
+ if (_entrance._startPosition.x < 1) {
+ // No entrance info either, so use defaults
+ if (IS_SERRATED_SCALPEL) {
+ hSavedPos = Point32(160 * FIXED_INT_MULTIPLIER, 100 * FIXED_INT_MULTIPLIER);
+ hSavedFacing = 4;
+ } else {
+ hSavedPos = people[HOLMES]._position;
+ hSavedFacing = people[HOLMES]._sequenceNumber;
+ }
+ } else {
+ // setup entrance info
+ hSavedPos.x = _entrance._startPosition.x * FIXED_INT_MULTIPLIER;
+ hSavedPos.y = _entrance._startPosition.y * FIXED_INT_MULTIPLIER;
+ if (IS_SERRATED_SCALPEL) {
+ hSavedPos.x /= 100;
+ hSavedPos.y /= 100;
+ }
+
+ hSavedFacing = _entrance._startDir;
+ }
+ } else {
+ // Exit information exists, translate it to real sequence info
+ // Note: If a savegame was just loaded, then the data is already correct.
+ // Otherwise, this is a linked scene or entrance info, and must be translated
+ if (hSavedFacing < 8 && !saves._justLoaded) {
+ hSavedFacing = FS_TRANS[hSavedFacing];
+ hSavedPos.x *= FIXED_INT_MULTIPLIER;
+ hSavedPos.y *= FIXED_INT_MULTIPLIER;
+ }
+ }
+
+ int cAnimNum = -1;
+
+ if (hSavedFacing < 101) {
+ // Standard info, so set it
+ people[HOLMES]._position = hSavedPos;
+ people[HOLMES]._sequenceNumber = hSavedFacing;
+
+ if (saves._justLoaded && IS_ROSE_TATTOO) {
+ Tattoo::TattooUserInterface &ui = *(Tattoo::TattooUserInterface *)_vm->_ui;
+
+ // For scrolling scenes, make sure the player is on-screen
+ ui._targetScroll.x = CLIP(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER -
+ SHERLOCK_SCREEN_WIDTH / 8 - 250, 0, screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH);
+ screen._currentScroll = ui._targetScroll;
+ }
+ } else {
+ // It's canimation information
+ cAnimNum = hSavedFacing - 101;
+ }
+
+ // Reset positioning for next load
+ hSavedPos = Common::Point(-1, -1);
+ hSavedFacing = -1;
+
+ if (cAnimNum != -1) {
+ // Prevent Holmes from being drawn
+ people[HOLMES]._position = Common::Point(0, 0);
+ }
+
+ for (uint objIdx = 0; objIdx < _bgShapes.size(); ++objIdx) {
+ Object &obj = _bgShapes[objIdx];
+
+ if (obj._aType > 1 && obj._type != INVALID && obj._type != HIDDEN) {
+ Common::Point topLeft = obj._position;
+ Common::Point bottomRight;
+
+ if (obj._type != NO_SHAPE) {
+ topLeft += obj._imageFrame->_offset;
+ bottomRight.x = topLeft.x + obj._imageFrame->_frame.w;
+ bottomRight.y = topLeft.y + obj._imageFrame->_frame.h;
+ } else {
+ bottomRight = topLeft + obj._noShapeSize;
+ }
+
+ if (Common::Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y).contains(
+ Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
+ people[HOLMES]._position.y / FIXED_INT_MULTIPLIER))) {
+ // Current point is already inside box - impact occurred on
+ // a previous call. So simply do nothing except talk until the
+ // player is clear of the box
+ switch (obj._aType) {
+ case FLAG_SET:
+ for (int useNum = 0; useNum < USE_COUNT; ++useNum) {
+ if (obj._use[useNum]._useFlag) {
+ if (!_vm->readFlags(obj._use[useNum]._useFlag))
+ _vm->setFlags(obj._use[useNum]._useFlag);
+ }
+
+ if (!talk._talkToAbort) {
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ toggleObject(obj._use[useNum]._names[nameIdx]);
+ }
+ }
+ }
+
+ obj._type = HIDDEN;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ updateBackground();
+
+ // Actually do the transition
+ if (screen._fadeStyle) {
+ if (!IS_3DO) {
+ // do pixel-transition for PC
+ screen.randomTransition();
+ } else {
+ // fade in for 3DO
+ screen.clear();
+ screen.fadeIntoScreen3DO(3);
+ }
+ } else {
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+ screen.update();
+
+ // Start any initial animation for the scene
+ if (cAnimNum != -1) {
+ CAnim &c = _cAnim[cAnimNum];
+ PositionFacing pt = c._goto[0];
+
+ c._goto[0].x = c._goto[0].y = -1;
+ people[HOLMES]._position = Common::Point(0, 0);
+
+ startCAnim(cAnimNum, 1);
+ c._goto[0] = pt;
+ }
+}
+
+int Scene::toggleObject(const Common::String &name) {
+ int count = 0;
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (name.equalsIgnoreCase(_bgShapes[idx]._name)) {
+ ++count;
+ _bgShapes[idx].toggleHidden();
+ }
+ }
+
+ return count;
+}
+
+void Scene::updateBackground() {
+ People &people = *_vm->_people;
+
+ // Update Holmes if he's turned on
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER)
+ people[idx].adjustSprite();
+ }
+
+ // Flag the bg shapes which need to be redrawn
+ checkBgShapes();
+
+ // Draw the shapes for the scene
+ drawAllShapes();
+}
+
+Exit *Scene::checkForExit(const Common::Rect &r) {
+ for (uint idx = 0; idx < _exits.size(); ++idx) {
+ if (_exits[idx].intersects(r))
+ return &_exits[idx];
+ }
+
+ return nullptr;
+}
+
+int Scene::findBgShape(const Common::Point &pt) {
+ if (!_doBgAnimDone)
+ // New frame hasn't been drawn yet
+ return -1;
+
+ for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN
+ && o._aType <= PERSON) {
+ if (o.getNewBounds().contains(pt))
+ return idx;
+ } else if (o._type == NO_SHAPE) {
+ if (o.getNoShapeBounds().contains(pt))
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+int Scene::checkForZones(const Common::Point &pt, int zoneType) {
+ int matches = 0;
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &o = _bgShapes[idx];
+ if ((o._aType == zoneType && o._type != INVALID) && o._type != HIDDEN) {
+ Common::Rect r = o._type == NO_SHAPE ? o.getNoShapeBounds() : o.getNewBounds();
+
+ if (r.contains(pt)) {
+ ++matches;
+ o.setFlagsAndToggles();
+ _vm->_talk->talkTo(o._use[0]._target);
+ }
+ }
+ }
+
+ return matches;
+}
+
+int Scene::whichZone(const Common::Point &pt) {
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ if (_zones[idx].contains(pt))
+ return idx;
+ }
+
+ return -1;
+}
+
+void Scene::synchronize(Serializer &s) {
+ if (s.isSaving())
+ saveSceneStatus();
+
+ if (s.isSaving()) {
+ s.syncAsSint16LE(_currentScene);
+ } else {
+ s.syncAsSint16LE(_goToScene);
+ _loadingSavedGame = true;
+ }
+
+ for (int sceneNum = 0; sceneNum < SCENES_COUNT; ++sceneNum) {
+ for (int flag = 0; flag < 65; ++flag) {
+ s.syncAsByte(_sceneStats[sceneNum][flag]);
+ }
+ }
+}
+
+void Scene::checkBgShapes() {
+ People &people = *_vm->_people;
+ Person &holmes = people[HOLMES];
+ Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);
+
+ // Iterate through the shapes
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+ if (obj._type == ACTIVE_BG_SHAPE || (IS_SERRATED_SCALPEL && obj._type == STATIC_BG_SHAPE)) {
+ if ((obj._flags & 5) == 1) {
+ obj._misc = (pt.y < (obj._position.y + obj.frameHeight() - 1)) ?
+ NORMAL_FORWARD : NORMAL_BEHIND;
+ } else if (!(obj._flags & OBJ_BEHIND)) {
+ obj._misc = BEHIND;
+ } else if (obj._flags & OBJ_FORWARD) {
+ obj._misc = FORWARD;
+ }
+ }
+ }
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h
new file mode 100644
index 0000000000..0fbda38fe8
--- /dev/null
+++ b/engines/sherlock/scene.h
@@ -0,0 +1,323 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCENE_H
+#define SHERLOCK_SCENE_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "sherlock/objects.h"
+#include "sherlock/resources.h"
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+
+#define MAX_ZONES 40
+#define INFO_LINE 140
+
+class SherlockEngine;
+
+struct BgFileHeader {
+ int _numStructs;
+ int _numImages;
+ int _numcAnimations;
+ int _descSize;
+ int _seqSize;
+
+ // Serrated Scalpel
+ int _fill;
+
+ // Rose Tattoo
+ int _scrollSize;
+ int _bytesWritten; // Size of the main body of the RRM
+ int _fadeStyle; // Fade style
+ byte _palette[PALETTE_SIZE]; // Palette
+
+
+ BgFileHeader();
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+};
+
+struct BgFileHeaderInfo {
+ int _filesize; // How long images are
+ int _maxFrames; // How many unique frames in object
+ Common::String _filename; // Filename of object
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
+};
+
+class Exit: public Common::Rect {
+public:
+ int _scene;
+ int _allow;
+ PositionFacing _newPosition;
+
+ Common::String _dest;
+ int _image; // Arrow image to use
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+ void load3DO(Common::SeekableReadStream &s);
+};
+
+struct SceneEntry {
+ Common::Point _startPosition;
+ int _startDir;
+ int _allow;
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
+};
+
+struct SceneSound {
+ Common::String _name;
+ int _priority;
+
+ /**
+ * Load the data for the object
+ */
+ void load(Common::SeekableReadStream &s);
+ void load3DO(Common::SeekableReadStream &s);
+};
+
+class ObjectArray : public Common::Array<Object> {
+public:
+ /**
+ * Retuurn the index of the passed object in the array
+ */
+ int indexOf(const Object &obj) const;
+};
+
+class ScaleZone: public Common::Rect {
+public:
+ int _topNumber; // Numerator of scale size at the top of the zone
+ int _bottomNumber; // Numerator of scale size at the bottom of the zone
+
+ void load(Common::SeekableReadStream &s);
+};
+
+class WalkArray : public Common::Array < Common::Point > {
+public:
+ int _pointsCount;
+ int _fileOffset;
+
+ WalkArray() : _pointsCount(0), _fileOffset(-1) {}
+
+ /**
+ * Load data for the walk array entry
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+};
+
+class Scene {
+private:
+ bool _loadingSavedGame;
+
+ /**
+ * Loads sounds for the scene
+ */
+ void loadSceneSounds();
+
+ /**
+ * Set objects to their current persistent state. This includes things such as
+ * opening or moving them
+ */
+ void checkSceneStatus();
+
+ /**
+ * Checks scene objects against the player's inventory items. If there are any
+ * matching names, it means the given item has already been picked up, and should
+ * be hidden in the scene.
+ */
+ void checkInventory();
+
+ /**
+ * Set up any entrance co-ordinates or entrance canimations, and then transition
+ * in the scene
+ */
+ void transitionToScene();
+
+ /**
+ * Restores objects to the correct status. This ensures that things like being opened or moved
+ * will remain the same on future visits to the scene
+ */
+ void saveSceneStatus();
+protected:
+ SherlockEngine *_vm;
+ Common::String _roomFilename;
+
+ /**
+ * Loads the data associated for a given scene. The room resource file's format is:
+ * BGHEADER: Holds an index for the rest of the file
+ * STRUCTS: The objects for the scene
+ * IMAGES: The graphic information for the structures
+ *
+ * The _misc field of the structures contains the number of the graphic image
+ * that it should point to after loading; _misc is then set to 0.
+ */
+ virtual bool loadScene(const Common::String &filename);
+
+ /**
+ * Checks all the background shapes. If a background shape is animating,
+ * it will flag it as needing to be drawn. If a non-animating shape is
+ * colliding with another shape, it will also flag it as needing drawing
+ */
+ virtual void checkBgShapes();
+
+ /**
+ * Draw all the shapes, people and NPCs in the correct order
+ */
+ virtual void drawAllShapes() = 0;
+
+ /**
+ * Called by loadScene when the palette is loaded for Rose Tattoo
+ */
+ virtual void paletteLoaded() {}
+
+ Scene(SherlockEngine *vm);
+public:
+ int _currentScene;
+ int _goToScene;
+ bool **_sceneStats;
+ bool _walkedInScene;
+ int _version;
+ bool _compressed;
+ int _invGraphicItems;
+ Common::String _comments;
+ Common::Array<char> _descText;
+ Common::Array<Common::Rect> _zones;
+ Common::Array<Object> _bgShapes;
+ Common::Array<CAnim> _cAnim;
+ Common::Array<byte> _sequenceBuffer;
+ Common::Array<SceneImage> _images;
+ int _walkDirectory[MAX_ZONES][MAX_ZONES];
+ Common::Array<WalkArray> _walkPoints;
+ Common::Array<Exit> _exits;
+ SceneEntry _entrance;
+ Common::Array<SceneSound> _sounds;
+ ObjectArray _canimShapes;
+ Common::Array<ScaleZone> _scaleZones;
+ Common::StringArray _objSoundList;
+ bool _restoreFlag;
+ int _animating;
+ bool _doBgAnimDone;
+ int _tempFadeStyle;
+ int _cAnimFramePause;
+public:
+ static Scene *init(SherlockEngine *vm);
+ virtual ~Scene();
+
+ /**
+ * Handles loading the scene specified by _goToScene
+ */
+ void selectScene();
+
+ /**
+ * Fres all the graphics and other dynamically allocated data for the scene
+ */
+ void freeScene();
+
+ /**
+ * Check the scene's objects against the game flags. If false is passed,
+ * it means the scene has just been loaded. A value of true means that the scene
+ * is in use (ie. not just loaded)
+ */
+ void checkSceneFlags(bool mode);
+
+ /**
+ * Check whether the passed area intersects with one of the scene's exits
+ */
+ Exit *checkForExit(const Common::Rect &r);
+
+ /**
+ * Scans through the object list to find one with a matching name, and will
+ * call toggleHidden with all matches found. Returns the numer of matches found
+ */
+ int toggleObject(const Common::String &name);
+
+ /**
+ * Checks to see if the given position in the scene belongs to a given zone type.
+ * If it is, the zone is activated and used just like a TAKL zone or aFLAG_SET zone.
+ */
+ int checkForZones(const Common::Point &pt, int zoneType);
+
+ /**
+ * Check which zone the the given position is located in.
+ */
+ int whichZone(const Common::Point &pt);
+
+ /**
+ * Returns the index of the closest zone to a given point.
+ */
+ virtual int closestZone(const Common::Point &pt) = 0;
+
+ /**
+ * Attempts to find a background shape within the passed bounds. If found,
+ * it will return the shape number, or -1 on failure.
+ */
+ virtual int findBgShape(const Common::Point &pt);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+public:
+ /**
+ * Draw all objects and characters.
+ */
+ virtual void doBgAnim() = 0;
+
+ /**
+ * Update the screen back buffer with all of the scene objects which need
+ * to be drawn
+ */
+ virtual void updateBackground();
+
+ /**
+ * Attempt to start a canimation sequence. It will load the requisite graphics, and
+ * then copy the canim object into the _canimShapes array to start the animation.
+ *
+ * @param cAnimNum The canim object within the current scene
+ * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc.
+ * A negative playRate can also be specified to play the animation in reverse
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1) = 0;
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp
new file mode 100644
index 0000000000..782869d77e
--- /dev/null
+++ b/engines/sherlock/screen.cpp
@@ -0,0 +1,553 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/screen.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/scalpel_screen.h"
+#include "common/system.h"
+#include "common/util.h"
+#include "graphics/palette.h"
+
+namespace Sherlock {
+
+Screen *Screen::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelScreen(vm);
+ else
+ return new Screen(vm);
+}
+
+Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->getHeight()), _vm(vm),
+ _backBuffer1(g_system->getWidth(), g_system->getHeight()),
+ _backBuffer2(g_system->getWidth(), g_system->getHeight()),
+ _backBuffer(&_backBuffer1) {
+ _transitionSeed = 1;
+ _fadeStyle = false;
+ Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0);
+ Common::fill(&_sMap[0], &_sMap[PALETTE_SIZE], 0);
+ Common::fill(&_tMap[0], &_tMap[PALETTE_SIZE], 0);
+
+ // Set up the initial font
+ setFont(IS_SERRATED_SCALPEL ? 1 : 4);
+
+ // Rose Tattoo specific fields
+ _fadeBytesRead = _fadeBytesToRead = 0;
+ _oldFadePercent = 0;
+ _flushScreen = false;
+}
+
+Screen::~Screen() {
+ Fonts::free();
+}
+
+void Screen::update() {
+ // Merge the dirty rects
+ mergeDirtyRects();
+
+ // Loop through copying dirty areas to the physical screen
+ Common::List<Common::Rect>::iterator i;
+ for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) {
+ const Common::Rect &r = *i;
+ const byte *srcP = (const byte *)getBasePtr(r.left, r.top);
+ g_system->copyRectToScreen(srcP, _surface.pitch, r.left, r.top,
+ r.width(), r.height());
+ }
+
+ // Signal the physical screen to update
+ g_system->updateScreen();
+ _dirtyRects.clear();
+}
+
+void Screen::makeAllDirty() {
+ addDirtyRect(Common::Rect(0, 0, this->w(), this->h()));
+}
+
+void Screen::getPalette(byte palette[PALETTE_SIZE]) {
+ g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT);
+}
+
+void Screen::setPalette(const byte palette[PALETTE_SIZE]) {
+ g_system->getPaletteManager()->setPalette(palette, 0, PALETTE_COUNT);
+}
+
+int Screen::equalizePalette(const byte palette[PALETTE_SIZE]) {
+ int total = 0;
+ byte tempPalette[PALETTE_SIZE];
+ getPalette(tempPalette);
+
+ // For any palette component that doesn't already match the given destination
+ // palette, change by 1 towards the reference palette component
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx) {
+ if (tempPalette[idx] > palette[idx]) {
+ tempPalette[idx] = MAX((int)palette[idx], (int)tempPalette[idx] - 4);
+ ++total;
+ } else if (tempPalette[idx] < palette[idx]) {
+ tempPalette[idx] = MIN((int)palette[idx], (int)tempPalette[idx] + 4);
+ ++total;
+ }
+ }
+
+ if (total > 0)
+ // Palette changed, so reload it
+ setPalette(tempPalette);
+
+ return total;
+}
+
+void Screen::fadeToBlack(int speed) {
+ byte tempPalette[PALETTE_SIZE];
+ Common::fill(&tempPalette[0], &tempPalette[PALETTE_SIZE], 0);
+
+ while (equalizePalette(tempPalette)) {
+ _vm->_events->delay(15 * speed);
+ }
+
+ setPalette(tempPalette);
+ fillRect(Common::Rect(0, 0, _surface.w, _surface.h), 0);
+}
+
+void Screen::fadeIn(const byte palette[PALETTE_SIZE], int speed) {
+ int count = 50;
+ while (equalizePalette(palette) && --count) {
+ _vm->_events->delay(15 * speed);
+ }
+
+ setPalette(palette);
+}
+
+void Screen::addDirtyRect(const Common::Rect &r) {
+ _dirtyRects.push_back(r);
+ assert(r.width() > 0 && r.height() > 0);
+}
+
+void Screen::mergeDirtyRects() {
+ Common::List<Common::Rect>::iterator rOuter, rInner;
+
+ // Process the dirty rect list to find any rects to merge
+ for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != _dirtyRects.end()) {
+
+ if ((*rOuter).intersects(*rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ unionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ _dirtyRects.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+}
+
+bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) {
+ destRect = src1;
+ destRect.extend(src2);
+
+ return !destRect.isEmpty();
+}
+
+void Screen::randomTransition() {
+ Events &events = *_vm->_events;
+ const int TRANSITION_MULTIPLIER = 0x15a4e35;
+ _dirtyRects.clear();
+ assert(IS_SERRATED_SCALPEL);
+
+ for (int idx = 0; idx <= 65535 && !_vm->shouldQuit(); ++idx) {
+ _transitionSeed = _transitionSeed * TRANSITION_MULTIPLIER + 1;
+ int offset = _transitionSeed & 0xFFFF;
+
+ if (offset < (this->w() * this->h()))
+ *((byte *)getPixels() + offset) = *((const byte *)_backBuffer->getPixels() + offset);
+
+ if (idx != 0 && (idx % 300) == 0) {
+ // Ensure there's a full screen dirty rect for the next frame update
+ if (_dirtyRects.empty())
+ addDirtyRect(Common::Rect(0, 0, _surface.w, _surface.h));
+
+ events.pollEvents();
+ events.delay(1);
+ }
+ }
+
+ // Make sure everything has been transferred
+ blitFrom(*_backBuffer);
+}
+
+void Screen::verticalTransition() {
+ Events &events = *_vm->_events;
+
+ byte table[640];
+ Common::fill(&table[0], &table[640], 0);
+
+ for (int yp = 0; yp < this->h(); ++yp) {
+ for (int xp = 0; xp < this->w(); ++xp) {
+ int temp = (table[xp] >= (this->h() - 3)) ? this->h() - table[xp] :
+ _vm->getRandomNumber(3) + 1;
+
+ if (temp) {
+ blitFrom(_backBuffer1, Common::Point(xp, table[xp]),
+ Common::Rect(xp, table[xp], xp + 1, table[xp] + temp));
+ table[xp] += temp;
+ }
+ }
+
+ events.delay(10);
+ }
+}
+
+void Screen::fadeIntoScreen3DO(int speed) {
+ Events &events = *_vm->_events;
+ uint16 *currentScreenBasePtr = (uint16 *)getPixels();
+ uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels();
+ uint16 currentScreenPixel = 0;
+ uint16 targetScreenPixel = 0;
+
+ uint16 currentScreenPixelRed = 0;
+ uint16 currentScreenPixelGreen = 0;
+ uint16 currentScreenPixelBlue = 0;
+
+ uint16 targetScreenPixelRed = 0;
+ uint16 targetScreenPixelGreen = 0;
+ uint16 targetScreenPixelBlue = 0;
+
+ uint16 screenWidth = this->w();
+ uint16 screenHeight = this->h();
+ uint16 screenX = 0;
+ uint16 screenY = 0;
+ uint16 pixelsChanged = 0;
+
+ _dirtyRects.clear();
+
+ do {
+ pixelsChanged = 0;
+ uint16 *currentScreenPtr = currentScreenBasePtr;
+ uint16 *targetScreenPtr = targetScreenBasePtr;
+
+ for (screenY = 0; screenY < screenHeight; screenY++) {
+ for (screenX = 0; screenX < screenWidth; screenX++) {
+ currentScreenPixel = *currentScreenPtr;
+ targetScreenPixel = *targetScreenPtr;
+
+ if (currentScreenPixel != targetScreenPixel) {
+ // pixel doesn't match, adjust accordingly
+ currentScreenPixelRed = currentScreenPixel & 0xF800;
+ currentScreenPixelGreen = currentScreenPixel & 0x07E0;
+ currentScreenPixelBlue = currentScreenPixel & 0x001F;
+ targetScreenPixelRed = targetScreenPixel & 0xF800;
+ targetScreenPixelGreen = targetScreenPixel & 0x07E0;
+ targetScreenPixelBlue = targetScreenPixel & 0x001F;
+
+ if (currentScreenPixelRed != targetScreenPixelRed) {
+ if (currentScreenPixelRed < targetScreenPixelRed) {
+ currentScreenPixelRed += 0x0800;
+ } else {
+ currentScreenPixelRed -= 0x0800;
+ }
+ }
+ if (currentScreenPixelGreen != targetScreenPixelGreen) {
+ // Adjust +2/-2 because we are running RGB555 at RGB565
+ if (currentScreenPixelGreen < targetScreenPixelGreen) {
+ currentScreenPixelGreen += 0x0040;
+ } else {
+ currentScreenPixelGreen -= 0x0040;
+ }
+ }
+ if (currentScreenPixelBlue != targetScreenPixelBlue) {
+ if (currentScreenPixelBlue < targetScreenPixelBlue) {
+ currentScreenPixelBlue += 0x0001;
+ } else {
+ currentScreenPixelBlue -= 0x0001;
+ }
+ }
+ *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
+ pixelsChanged++;
+ }
+
+ currentScreenPtr++;
+ targetScreenPtr++;
+ }
+ }
+
+ // Too much considered dirty at the moment
+ addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
+
+ events.pollEvents();
+ events.delay(10 * speed);
+ } while ((pixelsChanged) && (!_vm->shouldQuit()));
+}
+
+void Screen::blitFrom3DOcolorLimit(uint16 limitColor) {
+ uint16 *currentScreenPtr = (uint16 *)getPixels();
+ uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels();
+ uint16 currentScreenPixel = 0;
+
+ uint16 screenWidth = this->w();
+ uint16 screenHeight = this->h();
+ uint16 screenX = 0;
+ uint16 screenY = 0;
+
+ uint16 currentScreenPixelRed = 0;
+ uint16 currentScreenPixelGreen = 0;
+ uint16 currentScreenPixelBlue = 0;
+
+ uint16 limitPixelRed = limitColor & 0xF800;
+ uint16 limitPixelGreen = limitColor & 0x07E0;
+ uint16 limitPixelBlue = limitColor & 0x001F;
+
+ for (screenY = 0; screenY < screenHeight; screenY++) {
+ for (screenX = 0; screenX < screenWidth; screenX++) {
+ currentScreenPixel = *targetScreenPtr;
+
+ currentScreenPixelRed = currentScreenPixel & 0xF800;
+ currentScreenPixelGreen = currentScreenPixel & 0x07E0;
+ currentScreenPixelBlue = currentScreenPixel & 0x001F;
+
+ if (currentScreenPixelRed < limitPixelRed)
+ currentScreenPixelRed = limitPixelRed;
+ if (currentScreenPixelGreen < limitPixelGreen)
+ currentScreenPixelGreen = limitPixelGreen;
+ if (currentScreenPixelBlue < limitPixelBlue)
+ currentScreenPixelBlue = limitPixelBlue;
+
+ *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue;
+ currentScreenPtr++;
+ targetScreenPtr++;
+ }
+ }
+
+ // Too much considered dirty at the moment
+ addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight));
+}
+
+void Screen::restoreBackground(const Common::Rect &r) {
+ if (r.width() > 0 && r.height() > 0)
+ _backBuffer1.blitFrom(_backBuffer2, Common::Point(r.left, r.top), r);
+}
+
+void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) {
+ slamRect(Common::Rect(xp, yp, xp + width, yp + height));
+}
+
+void Screen::slamRect(const Common::Rect &r) {
+ if (r.width() && r.height() > 0) {
+ Common::Rect srcRect = r, destRect = r;
+
+ destRect.translate(-_currentScroll.x, -_currentScroll.y);
+
+ if (destRect.left < 0) {
+ srcRect.left += -destRect.left;
+ destRect.left = 0;
+ }
+ if (destRect.top < 0) {
+ srcRect.top += -destRect.top;
+ destRect.top = 0;
+ }
+ if (destRect.right > SHERLOCK_SCREEN_WIDTH) {
+ srcRect.right -= (destRect.left - SHERLOCK_SCREEN_WIDTH);
+ destRect.right = SHERLOCK_SCREEN_WIDTH;
+ }
+ if (destRect.bottom > SHERLOCK_SCREEN_HEIGHT) {
+ srcRect.bottom -= (destRect.bottom - SHERLOCK_SCREEN_HEIGHT);
+ destRect.bottom = SHERLOCK_SCREEN_HEIGHT;
+ }
+
+ if (srcRect.isValidRect())
+ blitFrom(*_backBuffer, Common::Point(destRect.left, destRect.top), srcRect);
+ }
+}
+
+
+void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
+ int16 *width, int16 *height) {
+ Common::Point imgPos = pt + frame->_offset;
+ Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->_frame.w, imgPos.y + frame->_frame.h);
+ Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height);
+
+ if (!_flushScreen) {
+ // See if the areas of the old and new overlap, and if so combine the areas
+ if (newBounds.intersects(oldBounds)) {
+ Common::Rect mergedBounds = newBounds;
+ mergedBounds.extend(oldBounds);
+ mergedBounds.right += 1;
+ mergedBounds.bottom += 1;
+
+ slamRect(mergedBounds);
+ } else {
+ // The two areas are independent, so copy them both
+ slamRect(newBounds);
+ slamRect(oldBounds);
+ }
+ }
+
+ *xp = newBounds.left;
+ *yp = newBounds.top;
+ *width = newBounds.width();
+ *height = newBounds.height();
+}
+
+void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
+ int16 *width, int16 *height, int scaleVal) {
+ Common::Point imgPos = pt + frame->_offset;
+ Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->sDrawXSize(scaleVal),
+ imgPos.y + frame->sDrawYSize(scaleVal));
+ Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height);
+
+ if (!_flushScreen) {
+ // See if the areas of the old and new overlap, and if so combine the areas
+ if (newBounds.intersects(oldBounds)) {
+ Common::Rect mergedBounds = newBounds;
+ mergedBounds.extend(oldBounds);
+ mergedBounds.right += 1;
+ mergedBounds.bottom += 1;
+
+ slamRect(mergedBounds);
+ } else {
+ // The two areas are independent, so copy them both
+ slamRect(newBounds);
+ slamRect(oldBounds);
+ }
+ }
+
+ *xp = newBounds.left;
+ *yp = newBounds.top;
+ *width = newBounds.width();
+ *height = newBounds.height();
+}
+
+void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal) {
+ Common::Point newPos, newSize;
+
+ if (scaleVal == 256)
+ flushImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y);
+ else
+ flushScaleImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y, scaleVal);
+
+ // Transfer the pos and size amounts into a single bounds rect
+ newBounds = Common::Rect(newPos.x, newPos.y, newPos.x + newSize.x, newPos.y + newSize.y);
+}
+
+void Screen::blockMove(const Common::Rect &r) {
+ Common::Rect bounds = r;
+ slamRect(bounds);
+}
+
+void Screen::blockMove() {
+ blockMove(Common::Rect(0, 0, w(), h()));
+}
+
+void Screen::print(const Common::Point &pt, byte color, const char *formatStr, ...) {
+ // Create the string to display
+ va_list args;
+ va_start(args, formatStr);
+ Common::String str = Common::String::vformat(formatStr, args);
+ va_end(args);
+
+ // Figure out area to draw text in
+ Common::Point pos = pt;
+ int width = stringWidth(str);
+ pos.y--; // Font is always drawing one line higher
+ if (!pos.x)
+ // Center text horizontally
+ pos.x = (this->w() - width) / 2;
+
+ Common::Rect textBounds(pos.x, pos.y, pos.x + width, pos.y + _fontHeight);
+ if (textBounds.right > this->w())
+ textBounds.moveTo(this->w() - width, textBounds.top);
+ if (textBounds.bottom > this->h())
+ textBounds.moveTo(textBounds.left, this->h() - _fontHeight);
+
+ // Write out the string at the given position
+ writeString(str, Common::Point(textBounds.left, textBounds.top), color);
+
+ // Copy the affected area to the screen
+ slamRect(textBounds);
+}
+
+void Screen::gPrint(const Common::Point &pt, byte color, const char *formatStr, ...) {
+ // Create the string to display
+ va_list args;
+ va_start(args, formatStr);
+ Common::String str = Common::String::vformat(formatStr, args);
+ va_end(args);
+
+ // Print the text
+ writeString(str, pt, color);
+}
+
+void Screen::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) {
+ Fonts::writeString(_backBuffer, str, pt, overrideColor);
+}
+
+void Screen::vgaBar(const Common::Rect &r, int color) {
+ _backBuffer->fillRect(r, color);
+ slamRect(r);
+}
+
+void Screen::setDisplayBounds(const Common::Rect &r) {
+ _sceneSurface.setPixels(_backBuffer1.getBasePtr(r.left, r.top), r.width(), r.height(), _backBuffer1.getPixelFormat());
+
+ _backBuffer = &_sceneSurface;
+}
+
+void Screen::resetDisplayBounds() {
+ _backBuffer = &_backBuffer1;
+}
+
+Common::Rect Screen::getDisplayBounds() {
+ return (_backBuffer == &_sceneSurface) ? Common::Rect(0, 0, _sceneSurface.w(), _sceneSurface.h()) :
+ Common::Rect(0, 0, this->w(), this->h());
+}
+
+void Screen::synchronize(Serializer &s) {
+ int fontNumb = _fontNumber;
+ s.syncAsByte(fontNumb);
+ if (s.isLoading())
+ setFont(fontNumb);
+}
+
+void Screen::initPaletteFade(int bytesToRead) {
+ Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_sMap[0]);
+ Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_tMap[0]);
+
+ // Set how many bytes need to be read / have been read
+ _fadeBytesRead = 0;
+ _fadeBytesToRead = bytesToRead;
+ _oldFadePercent = 0;
+}
+
+int Screen::fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize) {
+ warning("TODO: fadeRead");
+ stream.read(buf, totalSize);
+ return totalSize;
+}
+
+void Screen::translatePalette(byte palette[PALETTE_SIZE]) {
+ for (int idx = 0; idx < PALETTE_SIZE; ++idx)
+ palette[idx] = VGA_COLOR_TRANS(palette[idx]);
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h
new file mode 100644
index 0000000000..2e0cef72ca
--- /dev/null
+++ b/engines/sherlock/screen.h
@@ -0,0 +1,243 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SCREEN_H
+#define SHERLOCK_SCREEN_H
+
+#include "common/list.h"
+#include "common/rect.h"
+#include "sherlock/surface.h"
+#include "sherlock/resources.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+#define PALETTE_SIZE 768
+#define PALETTE_COUNT 256
+#define VGA_COLOR_TRANS(x) ((x) * 255 / 63)
+#define BG_GREYSCALE_RANGE_END 229
+
+enum {
+ BLACK = 0,
+ INFO_BLACK = 1,
+ BORDER_COLOR = 237,
+ COMMAND_BACKGROUND = 4,
+ BUTTON_BACKGROUND = 235,
+ TALK_FOREGROUND = 12,
+ TALK_NULL = 16
+};
+
+class SherlockEngine;
+
+class Screen : public Surface {
+private:
+ SherlockEngine *_vm;
+ Common::List<Common::Rect> _dirtyRects;
+ uint32 _transitionSeed;
+ Surface _sceneSurface;
+
+ // Rose Tattoo fields
+ int _fadeBytesRead, _fadeBytesToRead;
+ int _oldFadePercent;
+private:
+ /**
+ * Merges together overlapping dirty areas of the screen
+ */
+ void mergeDirtyRects();
+
+ /**
+ * Returns the union of two dirty area rectangles
+ */
+ bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2);
+protected:
+ /**
+ * Adds a rectangle to the list of modified areas of the screen during the
+ * current frame
+ */
+ virtual void addDirtyRect(const Common::Rect &r);
+public:
+ Surface _backBuffer1, _backBuffer2;
+ Surface *_backBuffer;
+ bool _fadeStyle;
+ byte _cMap[PALETTE_SIZE];
+ byte _sMap[PALETTE_SIZE];
+ byte _tMap[PALETTE_SIZE];
+ bool _flushScreen;
+ Common::Point _currentScroll;
+public:
+ static Screen *init(SherlockEngine *vm);
+ Screen(SherlockEngine *vm);
+ virtual ~Screen();
+
+ /**
+ * Handles updating any dirty areas of the screen Surface object to the physical screen
+ */
+ void update();
+
+ /**
+ * Makes the whole screen dirty, Hack for 3DO movie playing
+ */
+ void makeAllDirty();
+
+ /**
+ * Return the currently active palette
+ */
+ void getPalette(byte palette[PALETTE_SIZE]);
+
+ /**
+ * Set the palette
+ */
+ void setPalette(const byte palette[PALETTE_SIZE]);
+
+ /**
+ * Fades from the currently active palette to the passed palette
+ */
+ int equalizePalette(const byte palette[PALETTE_SIZE]);
+
+ /**
+ * Fade out the palette to black
+ */
+ void fadeToBlack(int speed = 2);
+
+ /**
+ * Fade in a given palette
+ */
+ void fadeIn(const byte palette[PALETTE_SIZE], int speed = 2);
+
+ /**
+ * Do a random pixel transition in from _backBuffer surface to the screen
+ */
+ void randomTransition();
+
+ /**
+ * Transition to the surface from _backBuffer using a vertical transition
+ */
+ void verticalTransition();
+
+ /**
+ * Fade backbuffer 1 into screen (3DO RGB!)
+ */
+ void fadeIntoScreen3DO(int speed);
+
+ void blitFrom3DOcolorLimit(uint16 color);
+
+ /**
+ * Prints the text passed onto the back buffer at the given position and color.
+ * The string is then blitted to the screen
+ */
+ void print(const Common::Point &pt, byte color, const char *formatStr, ...) GCC_PRINTF(4, 5);
+
+ /**
+ * Print a strings onto the back buffer without blitting it to the screen
+ */
+ void gPrint(const Common::Point &pt, byte color, const char *formatStr, ...) GCC_PRINTF(4, 5);
+
+ /**
+ * Copies a section of the second back buffer into the main back buffer
+ */
+ void restoreBackground(const Common::Rect &r);
+
+ /**
+ * Copies a given area to the screen
+ */
+ void slamArea(int16 xp, int16 yp, int16 width, int16 height);
+
+ /**
+ * Copies a given area to the screen
+ */
+ void slamRect(const Common::Rect &r);
+
+ /**
+ * Copy an image from the back buffer to the screen, taking care of both the
+ * new area covered by the shape as well as the old area, which must be restored
+ */
+ void flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
+ int16 *width, int16 *height);
+
+ /**
+ * Similar to flushImage, this method takes in an extra parameter for the scale proporation,
+ * which affects the calculated bounds accordingly
+ */
+ void flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp,
+ int16 *width, int16 *height, int scaleVal);
+
+ /**
+ * Variation of flushImage/flushScaleImage that takes in and updates a rect
+ */
+ void flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal);
+
+ /**
+ * Copies data from the back buffer to the screen
+ */
+ void blockMove(const Common::Rect &r);
+
+ /**
+ * Copies the entire screen from the back buffer
+ */
+ void blockMove();
+
+ /**
+ * Fills an area on the back buffer, and then copies it to the screen
+ */
+ void vgaBar(const Common::Rect &r, int color);
+
+ /**
+ * Sets the active back buffer pointer to a restricted sub-area of the first back buffer
+ */
+ void setDisplayBounds(const Common::Rect &r);
+
+ /**
+ * Resets the active buffer pointer to point back to the full first back buffer
+ */
+ void resetDisplayBounds();
+
+ /**
+ * Return the size of the current display window
+ */
+ Common::Rect getDisplayBounds();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+
+ /**
+ * Draws the given string into the back buffer using the images stored in _font
+ */
+ virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor);
+
+
+ // Rose Tattoo specific methods
+ void initPaletteFade(int bytesToRead);
+
+ int fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize);
+
+ /**
+ * Translate a palette from 6-bit RGB values to full 8-bit values suitable for passing
+ * to the underlying palette manager
+ */
+ static void translatePalette(byte palette[PALETTE_SIZE]);
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp
new file mode 100644
index 0000000000..18b9332867
--- /dev/null
+++ b/engines/sherlock/sherlock.cpp
@@ -0,0 +1,282 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/surface.h"
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+
+namespace Sherlock {
+
+SherlockEngine::SherlockEngine(OSystem *syst, const SherlockGameDescription *gameDesc) :
+ Engine(syst), _gameDescription(gameDesc), _randomSource("Sherlock") {
+ _animation = nullptr;
+ _debugger = nullptr;
+ _events = nullptr;
+ _fixedText = nullptr;
+ _inventory = nullptr;
+ _journal = nullptr;
+ _map = nullptr;
+ _music = nullptr;
+ _people = nullptr;
+ _res = nullptr;
+ _saves = nullptr;
+ _scene = nullptr;
+ _screen = nullptr;
+ _sound = nullptr;
+ _talk = nullptr;
+ _ui = nullptr;
+ _useEpilogue2 = false;
+ _loadGameSlot = -1;
+ _canLoadSave = false;
+ _showOriginalSavesDialog = false;
+ _interactiveFl = true;
+}
+
+SherlockEngine::~SherlockEngine() {
+ delete _animation;
+ delete _debugger;
+ delete _events;
+ delete _fixedText;
+ delete _journal;
+ delete _map;
+ delete _people;
+ delete _saves;
+ delete _scene;
+ delete _screen;
+ delete _music;
+ delete _sound;
+ delete _talk;
+ delete _ui;
+ delete _inventory;
+ delete _res;
+}
+
+void SherlockEngine::initialize() {
+ DebugMan.addDebugChannel(kDebugLevelScript, "scripts", "Script debug level");
+ DebugMan.addDebugChannel(kDebugLevelAdLibDriver, "AdLib", "AdLib driver debugging");
+ DebugMan.addDebugChannel(kDebugLevelMT32Driver, "MT32", "MT32 driver debugging");
+ DebugMan.addDebugChannel(kDebugLevelMusic, "Music", "Music debugging");
+
+ Fonts::setVm(this);
+ ImageFile::setVm(this);
+ ImageFile3DO::setVm(this);
+ BaseObject::setVm(this);
+
+ if (isDemo()) {
+ Common::File f;
+ // The interactive demo doesn't have an intro thus doesn't include TITLE.SND
+ // At the opposite, the non-interactive demo is only the intro.
+ if (f.exists("TITLE.SND"))
+ _interactiveFl = false;
+ }
+
+ _res = new Resources(this);
+ _animation = new Animation(this);
+ _debugger = Debugger::init(this);
+ _events = new Events(this);
+ _fixedText = FixedText::init(this);
+ _inventory = Inventory::init(this);
+ _map = Map::init(this);
+ _music = new Music(this, _mixer);
+ _journal = Journal::init(this);
+ _people = People::init(this);
+ _saves = SaveManager::init(this, _targetName);
+ _scene = Scene::init(this);
+ _screen = Screen::init(this);
+ _sound = new Sound(this, _mixer);
+ _talk = Talk::init(this);
+ _ui = UserInterface::init(this);
+
+ // Load game settings
+ loadConfig();
+
+ if (getPlatform() == Common::kPlatform3DO) {
+ // Disable portraits on 3DO
+ // 3DO does not include portrait data
+ _people->_portraitsOn = false;
+ }
+}
+
+Common::Error SherlockEngine::run() {
+ // Initialize the engine
+ initialize();
+
+ // Flag for whether to show original saves dialog rather than the ScummVM GMM
+ _showOriginalSavesDialog = ConfMan.getBool("originalsaveload");
+
+ // If requested, load a savegame instead of showing the intro
+ if (ConfMan.hasKey("save_slot")) {
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot >= 0 && saveSlot <= MAX_SAVEGAME_SLOTS)
+ _loadGameSlot = saveSlot;
+ }
+
+ if (_loadGameSlot != -1) {
+ _saves->loadGame(_loadGameSlot);
+ _loadGameSlot = -1;
+ } else {
+ do
+ showOpening();
+ while (!shouldQuit() && !_interactiveFl);
+ }
+
+ while (!shouldQuit()) {
+ // Prepare for scene, and handle any game-specific scenes. This allows
+ // for game specific cutscenes or mini-games that aren't standard scenes
+ startScene();
+ if (shouldQuit())
+ break;
+
+ // Clear the screen
+ _screen->clear();
+
+ // Reset UI flags
+ _ui->reset();
+
+ // Reset the data for the player character (Sherlock)
+ _people->reset();
+
+ // Initialize and load the scene.
+ _scene->selectScene();
+
+ // Scene handling loop
+ sceneLoop();
+ }
+
+ return Common::kNoError;
+}
+
+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 (_talk->_scriptMoreFlag == 1 || _talk->_scriptMoreFlag == 3)
+ _talk->talkTo(_talk->_scriptName);
+ else
+ _talk->_scriptMoreFlag = 0;
+
+ // Handle any input from the keyboard or mouse
+ handleInput();
+
+ if (_people->_savedPos.x == -1) {
+ _canLoadSave = true;
+ _scene->doBgAnim();
+ _canLoadSave = false;
+ }
+ }
+
+ _scene->freeScene();
+ _people->freeWalk();
+}
+
+void SherlockEngine::handleInput() {
+ _canLoadSave = _ui->_menuMode == STD_MODE;
+ _events->pollEventsAndWait();
+ _canLoadSave = false;
+
+ // See if a key or mouse button is pressed
+ _events->setButtonState();
+
+ _ui->handleInput();
+}
+
+bool SherlockEngine::readFlags(int flagNum) {
+ bool value = _flags[ABS(flagNum)];
+ if (flagNum < 0)
+ value = !value;
+
+ return value;
+}
+
+void SherlockEngine::setFlags(int flagNum) {
+ _flags[ABS(flagNum)] = flagNum >= 0;
+
+ _scene->checkSceneFlags(true);
+}
+
+void SherlockEngine::setFlagsDirect(int flagNum) {
+ _flags[ABS(flagNum)] = flagNum >= 0;
+}
+
+void SherlockEngine::loadConfig() {
+ // Load sound settings
+ syncSoundSettings();
+
+ ConfMan.registerDefault("font", getGameID() == GType_SerratedScalpel ? 1 : 4);
+
+ _screen->setFont(ConfMan.getInt("font"));
+ if (getGameID() == GType_SerratedScalpel)
+ _screen->_fadeStyle = ConfMan.getBool("fade_style");
+
+ _ui->_helpStyle = ConfMan.getBool("help_style");
+ _ui->_slideWindows = ConfMan.getBool("window_style");
+ _people->_portraitsOn = ConfMan.getBool("portraits_on");
+}
+
+void SherlockEngine::saveConfig() {
+ ConfMan.setBool("mute", !_sound->_digitized);
+ ConfMan.setBool("music_mute", !_music->_musicOn);
+ ConfMan.setBool("speech_mute", !_sound->_voices);
+
+ ConfMan.setInt("font", _screen->fontNumber());
+ ConfMan.setBool("fade_style", _screen->_fadeStyle);
+ ConfMan.setBool("help_style", _ui->_helpStyle);
+ ConfMan.setBool("window_style", _ui->_slideWindows);
+ ConfMan.setBool("portraits_on", _people->_portraitsOn);
+
+ ConfMan.flushToDisk();
+}
+
+void SherlockEngine::syncSoundSettings() {
+ Engine::syncSoundSettings();
+
+ // Load sound-related settings
+ _sound->syncSoundSettings();
+ _music->syncMusicSettings();
+}
+
+void SherlockEngine::synchronize(Serializer &s) {
+ for (uint idx = 0; idx < _flags.size(); ++idx)
+ s.syncAsByte(_flags[idx]);
+}
+
+bool SherlockEngine::canLoadGameStateCurrently() {
+ return _canLoadSave;
+}
+
+bool SherlockEngine::canSaveGameStateCurrently() {
+ return _canLoadSave;
+}
+
+Common::Error SherlockEngine::loadGameState(int slot) {
+ _saves->loadGame(slot);
+ return Common::kNoError;
+}
+
+Common::Error SherlockEngine::saveGameState(int slot, const Common::String &desc) {
+ _saves->saveGame(slot, desc);
+ return Common::kNoError;
+}
+
+} // End of namespace Comet
diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h
new file mode 100644
index 0000000000..69000e1fbc
--- /dev/null
+++ b/engines/sherlock/sherlock.h
@@ -0,0 +1,229 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_HOLMES_H
+#define SHERLOCK_HOLMES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/endian.h"
+#include "common/hash-str.h"
+#include "common/serializer.h"
+#include "common/random.h"
+#include "common/savefile.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "sherlock/animation.h"
+#include "sherlock/debugger.h"
+#include "sherlock/events.h"
+#include "sherlock/fixed_text.h"
+#include "sherlock/inventory.h"
+#include "sherlock/journal.h"
+#include "sherlock/map.h"
+#include "sherlock/music.h"
+#include "sherlock/people.h"
+#include "sherlock/resources.h"
+#include "sherlock/saveload.h"
+#include "sherlock/scene.h"
+#include "sherlock/screen.h"
+#include "sherlock/sound.h"
+#include "sherlock/talk.h"
+#include "sherlock/user_interface.h"
+
+namespace Sherlock {
+
+enum {
+ kDebugLevelScript = 1 << 0,
+ kDebugLevelAdLibDriver = 2 << 0,
+ kDebugLevelMT32Driver = 3 << 0,
+ kDebugLevelMusic = 4 << 0
+};
+
+enum GameType {
+ GType_SerratedScalpel = 0,
+ GType_RoseTattoo = 1
+};
+
+#define SHERLOCK_SCREEN_WIDTH _vm->_screen->w()
+#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h()
+#define SHERLOCK_SCENE_WIDTH _vm->_screen->_backBuffer1.w()
+#define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480)
+#define SCENES_COUNT (IS_SERRATED_SCALPEL ? 63 : 101)
+
+#define COL_INFO_FOREGROUND (IS_SERRATED_SCALPEL ? (byte)Scalpel::INFO_FOREGROUND : (byte)Tattoo::INFO_FOREGROUND)
+#define COL_PEN_COLOR (IS_SERRATED_SCALPEL ? (byte)Scalpel::PEN_COLOR : (byte)Tattoo::PEN_COLOR)
+
+struct SherlockGameDescription;
+
+class Resource;
+
+class SherlockEngine : public Engine {
+private:
+ /**
+ * Main loop for displaying a scene and handling all that occurs within it
+ */
+ void sceneLoop();
+
+ /**
+ * Handle all player input
+ */
+ void handleInput();
+
+ /**
+ * Load game configuration esttings
+ */
+ void loadConfig();
+protected:
+ /**
+ * Does basic initialization of the game engine
+ */
+ virtual void initialize();
+
+ virtual void showOpening() = 0;
+
+ virtual void startScene() {}
+
+ /**
+ * Returns a list of features the game itself supports
+ */
+ virtual bool hasFeature(EngineFeature f) const;
+public:
+ const SherlockGameDescription *_gameDescription;
+ Animation *_animation;
+ Debugger *_debugger;
+ Events *_events;
+ FixedText *_fixedText;
+ Inventory *_inventory;
+ Journal *_journal;
+ Map *_map;
+ Music *_music;
+ People *_people;
+ Resources *_res;
+ SaveManager *_saves;
+ Scene *_scene;
+ Screen *_screen;
+ Sound *_sound;
+ Talk *_talk;
+ UserInterface *_ui;
+ Common::RandomSource _randomSource;
+ Common::Array<bool> _flags;
+ bool _useEpilogue2;
+ int _loadGameSlot;
+ bool _canLoadSave;
+ bool _showOriginalSavesDialog;
+ bool _interactiveFl;
+public:
+ SherlockEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
+ virtual ~SherlockEngine();
+
+ /**
+ * Main method for running the game
+ */
+ virtual Common::Error run();
+
+ /**
+ * Returns true if a savegame can be loaded
+ */
+ virtual bool canLoadGameStateCurrently();
+
+ /**
+ * Returns true if the game can be saved
+ */
+ virtual bool canSaveGameStateCurrently();
+
+ /**
+ * Called by the GMM to load a savegame
+ */
+ virtual Common::Error loadGameState(int slot);
+
+ /**
+ * Called by the GMM to save the game
+ */
+ virtual Common::Error saveGameState(int slot, const Common::String &desc);
+
+ /**
+ * Called by the engine when sound settings are updated
+ */
+ virtual void syncSoundSettings();
+
+ /**
+ * Returns whether the version is a demo
+ */
+ virtual bool isDemo() const;
+
+ /**
+ * Returns the Id of the game
+ */
+ GameType getGameID() const;
+
+ /**
+ * Returns the platform the game's datafiles are for
+ */
+ Common::Platform getPlatform() const;
+
+ /**
+ * Return the game's language
+ */
+ Common::Language getLanguage() const;
+
+ /**
+ * Return a random number
+ */
+ int getRandomNumber(int limit) { return _randomSource.getRandomNumber(limit - 1); }
+
+ /**
+ * Read the state of a global flag
+ * @remarks If a negative value is specified, it will return the inverse value
+ * of the positive flag number
+ */
+ bool readFlags(int flagNum);
+
+ /**
+ * Sets a global flag to either true or false depending on whether the specified
+ * flag is positive or negative
+ */
+ void setFlags(int flagNum);
+
+ /**
+ * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive.
+ * @remarks We don't use the global setFlags method because we don't want to check scene flags
+ */
+ void setFlagsDirect(int flagNum);
+
+ /**
+ * Saves game configuration information
+ */
+ void saveConfig();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+};
+
+#define IS_ROSE_TATTOO (_vm->getGameID() == GType_RoseTattoo)
+#define IS_SERRATED_SCALPEL (_vm->getGameID() == GType_SerratedScalpel)
+#define IS_3DO (_vm->getPlatform() == Common::kPlatform3DO)
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp
new file mode 100644
index 0000000000..b46eb67b50
--- /dev/null
+++ b/engines/sherlock/sound.cpp
@@ -0,0 +1,269 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/sherlock.h"
+#include "sherlock/sound.h"
+#include "common/config-manager.h"
+#include "audio/audiostream.h"
+#include "common/algorithm.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/aiff.h"
+#include "audio/decoders/wave.h"
+
+namespace Sherlock {
+
+static const int8 creativeADPCM_ScaleMap[64] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 0, -1, -2, -3, -4, -5, -6, -7,
+ 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15,
+ 2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30,
+ 4, 12, 20, 28, 36, 44, 52, 60, -4, -12, -20, -28, -36, -44, -52, -60
+};
+
+static const uint8 creativeADPCM_AdjustMap[64] = {
+ 0, 0, 0, 0, 0, 16, 16, 16,
+ 0, 0, 0, 0, 0, 16, 16, 16,
+ 240, 0, 0, 0, 0, 16, 16, 16,
+ 240, 0, 0, 0, 0, 16, 16, 16,
+ 240, 0, 0, 0, 0, 16, 16, 16,
+ 240, 0, 0, 0, 0, 16, 16, 16,
+ 240, 0, 0, 0, 0, 0, 0, 0,
+ 240, 0, 0, 0, 0, 0, 0, 0
+};
+
+/*----------------------------------------------------------------*/
+
+Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) {
+ _digitized = false;
+ _voices = 0;
+ _diskSoundPlaying = false;
+ _soundPlaying = false;
+ _soundIsOn = &_soundPlaying;
+ _curPriority = 0;
+ _digiBuf = nullptr;
+
+ _soundOn = true;
+ _speechOn = true;
+
+ if (IS_3DO) {
+ // 3DO: we don't need to prepare anything for sound
+ return;
+ }
+
+ _vm->_res->addToCache("MUSIC.LIB");
+ if (!_vm->_interactiveFl)
+ _vm->_res->addToCache("TITLE.SND");
+ else {
+ _vm->_res->addToCache("MUSIC.LIB");
+
+ if (IS_ROSE_TATTOO) {
+ _vm->_res->addToCache("SOUND.LIB");
+ } else {
+ _vm->_res->addToCache("SND.SND");
+
+ if (!_vm->isDemo()) {
+ _vm->_res->addToCache("TITLE.SND");
+ _vm->_res->addToCache("EPILOGUE.SND");
+ }
+ }
+ }
+}
+
+void Sound::syncSoundSettings() {
+ _digitized = !ConfMan.getBool("mute");
+ _voices = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute") ? 1 : 0;
+}
+
+void Sound::loadSound(const Common::String &name, int priority) {
+ // No implementation required in ScummVM
+ //warning("loadSound");
+}
+
+byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) {
+ int16 samp = sample + scale;
+ int16 ref = 0;
+
+ // clip bad ADPCM-4 sample
+ samp = CLIP<int16>(samp, 0, 63);
+
+ ref = reference + creativeADPCM_ScaleMap[samp];
+ if (ref > 0xff) {
+ reference = 0xff;
+ } else {
+ if (ref < 0x00) {
+ reference = 0;
+ } else {
+ reference = (uint8)(ref & 0xff);
+ }
+ }
+
+ scale = (scale + creativeADPCM_AdjustMap[samp]) & 0xff;
+ return reference;
+}
+
+bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) {
+ Resources &res = *_vm->_res;
+ stopSound();
+
+ Common::String filename = name;
+ if (!filename.contains('.')) {
+ if (!IS_3DO) {
+ if (IS_SERRATED_SCALPEL) {
+ filename += ".SND";
+ } else {
+ filename += ".WAV";
+ }
+ } else {
+ // 3DO uses .aiff extension
+ filename += ".AIFF";
+ if (!filename.contains('/')) {
+ // if no directory was given, use the room sounds directory
+ filename = "rooms/sounds/" + filename;
+ }
+ }
+ }
+
+ Common::String libFilename(libraryFilename);
+ Common::SeekableReadStream *stream = libFilename.empty() ? res.load(filename) : res.load(filename, libFilename);
+
+ Audio::AudioStream *audioStream;
+
+ if (!IS_3DO) {
+ if (IS_SERRATED_SCALPEL) {
+ stream->skip(2);
+ int size = stream->readUint32BE();
+ int rate = stream->readUint16BE();
+ byte *data = (byte *)malloc(size);
+ byte *ptr = data;
+ stream->read(ptr, size);
+ delete stream;
+
+ assert(size > 2);
+
+ byte *decoded = (byte *)malloc((size - 1) * 2);
+
+ // Holmes uses Creative ADPCM 4-bit data
+ int counter = 0;
+ byte reference = ptr[0];
+ int16 scale = 0;
+
+ for(int i = 1; i < size; i++) {
+ decoded[counter++] = decodeSample((ptr[i]>>4)&0x0f, reference, scale);
+ decoded[counter++] = decodeSample((ptr[i]>>0)&0x0f, reference, scale);
+ }
+
+ free(data);
+
+#if 0
+ // Debug : used to dump files
+ Common::DumpFile outFile;
+ outFile.open(filename);
+ outFile.write(decoded, (size - 2) * 2);
+ outFile.flush();
+ outFile.close();
+#endif
+
+ audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+ } else {
+ audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
+ }
+ } else {
+ // 3DO: AIFF file
+ audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
+ }
+
+ Audio::SoundHandle effectsHandle = (IS_SERRATED_SCALPEL) ? _scalpelEffectsHandle : getFreeSoundHandle();
+
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &effectsHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume);
+ _soundPlaying = true;
+ _curPriority = priority;
+
+ if (waitType == WAIT_RETURN_IMMEDIATELY) {
+ _diskSoundPlaying = true;
+ return true;
+ }
+
+ bool retval = true;
+ do {
+ _vm->_events->pollEvents();
+ g_system->delayMillis(10);
+ if ((waitType == WAIT_KBD_OR_FINISH) && _vm->_events->kbHit()) {
+ retval = false;
+ break;
+ }
+ } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(effectsHandle));
+
+ _soundPlaying = false;
+ _mixer->stopHandle(effectsHandle);
+
+ return retval;
+}
+
+void Sound::playLoadedSound(int bufNum, WaitType waitType) {
+ if (IS_SERRATED_SCALPEL) {
+ if (_mixer->isSoundHandleActive(_scalpelEffectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority))
+ return;
+
+ stopSound();
+ }
+
+ playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority);
+}
+
+void Sound::freeLoadedSounds() {
+ // As sounds are played with DisposeAfterUse::YES, stopping the sounds also
+ // frees them
+ stopSound();
+}
+
+void Sound::stopSound() {
+ if (IS_SERRATED_SCALPEL) {
+ _mixer->stopHandle(_scalpelEffectsHandle);
+ } else {
+ for (int i = 0; i < MAX_MIXER_CHANNELS; i++)
+ _mixer->stopHandle(_tattooEffectsHandle[i]);
+ }
+}
+
+void Sound::stopSndFuncPtr(int v1, int v2) {
+ // TODO
+ warning("TODO: Sound::stopSndFuncPtr");
+}
+
+void Sound::freeDigiSound() {
+ delete[] _digiBuf;
+ _digiBuf = nullptr;
+ _diskSoundPlaying = false;
+ _soundPlaying = false;
+}
+
+Audio::SoundHandle Sound::getFreeSoundHandle() {
+ for (int i = 0; i < MAX_MIXER_CHANNELS; i++) {
+ if (!_mixer->isSoundHandleActive(_tattooEffectsHandle[i]))
+ return _tattooEffectsHandle[i];
+ }
+
+ error("getFreeSoundHandle: No sound handle found");
+}
+
+} // End of namespace Sherlock
+
diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h
new file mode 100644
index 0000000000..414b1ef49b
--- /dev/null
+++ b/engines/sherlock/sound.h
@@ -0,0 +1,106 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_SOUND_H
+#define SHERLOCK_SOUND_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "access/files.h"
+#include "audio/midiplayer.h"
+#include "audio/midiparser.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+enum WaitType {
+ WAIT_RETURN_IMMEDIATELY = 0, WAIT_FINISH = 1, WAIT_KBD_OR_FINISH = 2
+};
+
+#define MAX_MIXER_CHANNELS 10
+
+class Sound {
+private:
+ SherlockEngine *_vm;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _scalpelEffectsHandle;
+ Audio::SoundHandle _tattooEffectsHandle[MAX_MIXER_CHANNELS];
+ int _curPriority;
+
+ byte decodeSample(byte sample, byte& reference, int16& scale);
+public:
+ bool _digitized;
+ int _voices;
+ bool _soundOn;
+ bool _speechOn;
+ bool _diskSoundPlaying;
+ bool _soundPlaying;
+ bool *_soundIsOn;
+ byte *_digiBuf;
+
+ Common::String _talkSoundFile;
+public:
+ Sound(SherlockEngine *vm, Audio::Mixer *mixer);
+
+ /**
+ * Saves sound-related settings
+ */
+ void syncSoundSettings();
+
+ /**
+ * Load a sound
+ */
+ void loadSound(const Common::String &name, int priority);
+
+ /**
+ * Play the sound in the specified resource
+ */
+ bool playSound(const Common::String &name, WaitType waitType, int priority = 100, const char *libraryFilename = nullptr);
+
+ /**
+ * Play a previously loaded sound
+ */
+ void playLoadedSound(int bufNum, WaitType waitType);
+
+ /**
+ * Free any previously loaded sounds
+ */
+ void freeLoadedSounds();
+
+ /**
+ * Stop playing any active sound
+ */
+ void stopSound();
+
+ void stopSndFuncPtr(int v1, int v2);
+ void freeDigiSound();
+
+ Audio::SoundHandle getFreeSoundHandle();
+};
+
+} // End of namespace Sherlock
+
+#endif
+
diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp
new file mode 100644
index 0000000000..42194de7a3
--- /dev/null
+++ b/engines/sherlock/surface.cpp
@@ -0,0 +1,307 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/surface.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/resources.h"
+#include "common/system.h"
+#include "graphics/palette.h"
+
+namespace Sherlock {
+
+Surface::Surface(uint16 width, uint16 height) : Fonts(), _freePixels(true) {
+ create(width, height);
+}
+
+Surface::Surface() : Fonts(), _freePixels(false) {
+}
+
+Surface::~Surface() {
+ if (_freePixels)
+ _surface.free();
+}
+
+void Surface::create(uint16 width, uint16 height) {
+ if (_freePixels)
+ _surface.free();
+
+ if (_vm->getPlatform() == Common::kPlatform3DO) {
+ _surface.create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+ } else {
+ _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ }
+ _freePixels = true;
+}
+
+Graphics::PixelFormat Surface::getPixelFormat() {
+ return _surface.format;
+}
+
+void Surface::blitFrom(const Surface &src) {
+ blitFrom(src, Common::Point(0, 0));
+}
+
+void Surface::blitFrom(const ImageFrame &src) {
+ blitFrom(src._frame, Common::Point(0, 0));
+}
+
+void Surface::blitFrom(const Graphics::Surface &src) {
+ blitFrom(src, Common::Point(0, 0));
+}
+
+void Surface::blitFrom(const Surface &src, const Common::Point &pt) {
+ blitFrom(src, pt, Common::Rect(0, 0, src._surface.w, src._surface.h));
+}
+
+void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt) {
+ blitFrom(src._frame, pt, Common::Rect(0, 0, src._frame.w, src._frame.h));
+}
+
+void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt) {
+ blitFrom(src, pt, Common::Rect(0, 0, src.w, src.h));
+}
+
+void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) {
+ Common::Rect srcRect = srcBounds;
+ Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height());
+
+ if (srcRect.isValidRect() && clip(srcRect, destRect)) {
+ // Surface is at least partially or completely on-screen
+ addDirtyRect(destRect);
+ _surface.copyRectToSurface(src, destRect.left, destRect.top, srcRect);
+ }
+}
+
+void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds) {
+ blitFrom(src._frame, pt, srcBounds);
+}
+
+void Surface::blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) {
+ blitFrom(src._surface, pt, srcBounds);
+}
+
+void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt,
+ bool flipped, int overrideColor, int scaleVal) {
+ transBlitFrom(src._frame, pt + src._offset, flipped, overrideColor, scaleVal);
+}
+
+void Surface::transBlitFrom(const Surface &src, const Common::Point &pt,
+ bool flipped, int overrideColor, int scaleVal) {
+ const Graphics::Surface &s = src._surface;
+ transBlitFrom(s, pt, flipped, overrideColor, scaleVal);
+}
+
+void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
+ bool flipped, int overrideColor, int scaleVal) {
+ if (scaleVal == SCALE_THRESHOLD) {
+ transBlitFromUnscaled(src, pt, flipped, overrideColor);
+ return;
+ }
+
+ int scaleX = SCALE_THRESHOLD * SCALE_THRESHOLD / scaleVal;
+ int scaleY = scaleX;
+ int scaleXCtr = 0, scaleYCtr = 0;
+ int destX, destY;
+ int xCtr, yCtr;
+ int maxX = pt.x;
+
+ for (yCtr = 0, destY = pt.y; yCtr < src.h && destY < this->h(); ++yCtr) {
+ // Handle skipping lines if Y scaling
+ scaleYCtr += scaleY;
+
+ while (scaleYCtr >= SCALE_THRESHOLD && destY < this->h()) {
+ scaleYCtr -= SCALE_THRESHOLD;
+
+ if (destY >= 0) {
+ // Handle drawing the line
+ const byte *pSrc = (const byte *)src.getBasePtr(flipped ? src.w - 1 : 0, yCtr);
+ byte *pDest = (byte *)getBasePtr(pt.x, destY);
+ scaleXCtr = 0;
+
+ for (xCtr = 0, destX = pt.x; xCtr < src.w && destX < this->w(); ++xCtr) {
+ // Handle horizontal scaling
+ scaleXCtr += scaleX;
+
+ while (scaleXCtr >= SCALE_THRESHOLD && destX < this->w()) {
+ scaleXCtr -= SCALE_THRESHOLD;
+
+ // Only handle on-screen pixels
+ if (destX >= 0 && *pSrc != TRANSPARENCY)
+ *pDest = *pSrc;
+
+ ++pDest;
+ ++destX;
+ }
+
+ maxX = MAX(maxX, destX);
+ pSrc = pSrc + (flipped ? -1 : 1);
+ }
+ }
+
+ ++destY;
+ }
+ }
+
+ // Mark the affected area
+ addDirtyRect(Common::Rect(pt.x, pt.y, maxX, destY));
+}
+
+void Surface::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt,
+ bool flipped, int overrideColor) {
+ Common::Rect drawRect(0, 0, src.w, src.h);
+ Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h);
+
+ // Clip the display area to on-screen
+ if (!clip(drawRect, destRect))
+ // It's completely off-screen
+ return;
+
+ if (flipped)
+ drawRect = Common::Rect(src.w - drawRect.right, src.h - drawRect.bottom,
+ src.w - drawRect.left, src.h - drawRect.top);
+
+ Common::Point destPt(destRect.left, destRect.top);
+ addDirtyRect(Common::Rect(destPt.x, destPt.y, destPt.x + drawRect.width(),
+ destPt.y + drawRect.height()));
+
+ switch (src.format.bytesPerPixel) {
+ case 1:
+ // 8-bit palettized: Draw loop
+ assert(_surface.format.bytesPerPixel == 1); // Security check
+ for (int yp = 0; yp < drawRect.height(); ++yp) {
+ const byte *srcP = (const byte *)src.getBasePtr(
+ flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
+ byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp);
+
+ for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) {
+ if (*srcP != TRANSPARENCY)
+ *destP = overrideColor ? overrideColor : *srcP;
+
+ srcP = flipped ? srcP - 1 : srcP + 1;
+ }
+ }
+ break;
+ case 2:
+ // 3DO 15-bit RGB565: Draw loop
+ assert(_surface.format.bytesPerPixel == 2); // Security check
+ for (int yp = 0; yp < drawRect.height(); ++yp) {
+ const uint16 *srcP = (const uint16 *)src.getBasePtr(
+ flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp);
+ uint16 *destP = (uint16 *)getBasePtr(destPt.x, destPt.y + yp);
+
+ for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) {
+ if (*srcP) // RGB 0, 0, 0 -> transparent on 3DO
+ *destP = *srcP; // overrideColor ? overrideColor : *srcP;
+
+ srcP = flipped ? srcP - 1 : srcP + 1;
+ }
+ }
+ break;
+ default:
+ error("Surface: unsupported bytesperpixel");
+ break;
+ }
+}
+
+void Surface::fillRect(int x1, int y1, int x2, int y2, byte color) {
+ fillRect(Common::Rect(x1, y1, x2, y2), color);
+}
+
+void Surface::fillRect(const Common::Rect &r, byte color) {
+ _surface.fillRect(r, color);
+ addDirtyRect(r);
+}
+
+void Surface::fill(uint16 color) {
+ _surface.fillRect(Common::Rect(_surface.w, _surface.h), color);
+}
+
+bool Surface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) {
+ if (destBounds.left >= _surface.w || destBounds.top >= _surface.h ||
+ destBounds.right <= 0 || destBounds.bottom <= 0)
+ return false;
+
+ // Clip the bounds if necessary to fit on-screen
+ if (destBounds.right > _surface.w) {
+ srcBounds.right -= destBounds.right - _surface.w;
+ destBounds.right = _surface.w;
+ }
+
+ if (destBounds.bottom > _surface.h) {
+ srcBounds.bottom -= destBounds.bottom - _surface.h;
+ destBounds.bottom = _surface.h;
+ }
+
+ if (destBounds.top < 0) {
+ srcBounds.top += -destBounds.top;
+ destBounds.top = 0;
+ }
+
+ if (destBounds.left < 0) {
+ srcBounds.left += -destBounds.left;
+ destBounds.left = 0;
+ }
+
+ return true;
+}
+
+void Surface::clear() {
+ fillRect(Common::Rect(0, 0, _surface.w, _surface.h), 0);
+}
+
+void Surface::free() {
+ if (_freePixels) {
+ _surface.free();
+ _freePixels = false;
+ }
+}
+
+void Surface::setPixels(byte *pixels, int width, int height, Graphics::PixelFormat pixelFormat) {
+ _surface.format = pixelFormat;
+ _surface.w = width;
+ _surface.h = height;
+ _surface.pitch = width * pixelFormat.bytesPerPixel;
+ _surface.setPixels(pixels);
+}
+
+void Surface::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) {
+ Fonts::writeString(this, str, pt, overrideColor);
+}
+
+void Surface::writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2) {
+ writeString(str, Common::Point(pt.x, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y), overrideColor1);
+ writeString(str, Common::Point(pt.x, pt.y + 1), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y + 1), overrideColor1);
+ writeString(str, Common::Point(pt.x, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 2, pt.y + 2), overrideColor1);
+ writeString(str, Common::Point(pt.x + 1, pt.y + 1), overrideColor2);
+}
+
+void Surface::maskArea(const ImageFrame &src, const Common::Point &pt) {
+ // TODO
+ error("TODO: maskArea");
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h
new file mode 100644
index 0000000000..38e3339617
--- /dev/null
+++ b/engines/sherlock/surface.h
@@ -0,0 +1,185 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_GRAPHICS_H
+#define SHERLOCK_GRAPHICS_H
+
+#include "common/rect.h"
+#include "common/platform.h"
+#include "graphics/surface.h"
+#include "sherlock/fonts.h"
+
+namespace Sherlock {
+
+#define SCALE_THRESHOLD 0x100
+#define TRANSPARENCY 255
+
+struct ImageFrame;
+
+class Surface: public Fonts {
+private:
+ bool _freePixels;
+
+ /**
+ * Clips the given source bounds so the passed destBounds will be entirely on-screen
+ */
+ bool clip(Common::Rect &srcBounds, Common::Rect &destBounds);
+
+ /**
+ * Copy a surface into this one
+ */
+ void blitFrom(const Graphics::Surface &src);
+
+ /**
+ * Draws a surface at a given position within this surface
+ */
+ void blitFrom(const Graphics::Surface &src, const Common::Point &pt);
+
+ /**
+ * Draws a sub-section of a surface at a given position within this surface
+ */
+ void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds);
+
+ /**
+ * Draws a surface at a given position within this surface with transparency
+ */
+ void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped,
+ int overrideColor);
+
+protected:
+ Graphics::Surface _surface;
+
+ virtual void addDirtyRect(const Common::Rect &r) {}
+public:
+ Surface(uint16 width, uint16 height);
+ Surface();
+ virtual ~Surface();
+
+ /**
+ * Sets up an internal surface with the specified dimensions that will be automatically freed
+ * when the surface object is destroyed
+ */
+ void create(uint16 width, uint16 height);
+
+ Graphics::PixelFormat getPixelFormat();
+
+ /**
+ * Copy a surface into this one
+ */
+ void blitFrom(const Surface &src);
+
+ /**
+ * Copy an image frame into this surface
+ */
+ void blitFrom(const ImageFrame &src);
+
+ /**
+ * Draws a surface at a given position within this surface
+ */
+ void blitFrom(const Surface &src, const Common::Point &pt);
+
+ /**
+ * Copy an image frame onto this surface at a given position
+ */
+ void blitFrom(const ImageFrame &src, const Common::Point &pt);
+
+ /**
+ * Draws a sub-section of a surface at a given position within this surface
+ */
+ void blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds);
+
+ /**
+ * Copy a sub-area of a source image frame into this surface at a given position
+ */
+ void blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds);
+
+ /**
+ * Draws an image frame at a given position within this surface with transparency
+ */
+ void transBlitFrom(const ImageFrame &src, const Common::Point &pt,
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
+
+ /**
+ * Draws a surface at a given position within this surface with transparency
+ */
+ void transBlitFrom(const Surface &src, const Common::Point &pt,
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
+
+ /**
+ * Draws a surface at a given position within this surface with transparency
+ */
+ void transBlitFrom(const Graphics::Surface &src, const Common::Point &pt,
+ bool flipped = false, int overrideColor = 0, int scaleVal = 256);
+
+ /**
+ * Fill a given area of the surface with a given color
+ */
+ void fillRect(int x1, int y1, int x2, int y2, byte color);
+
+ /**
+ * Fill a given area of the surface with a given color
+ */
+ void fillRect(const Common::Rect &r, byte color);
+
+ void fill(uint16 color);
+
+ void maskArea(const ImageFrame &src, const Common::Point &pt);
+
+ /**
+ * Clear the surface
+ */
+ void clear();
+
+ /**
+ * Free the underlying surface
+ */
+ void free();
+
+ /**
+ * Returns true if the surface is empty
+ */
+ bool empty() const { return _surface.getPixels() == nullptr; }
+
+ /**
+ * Set the pixels for the surface to an existing data block
+ */
+ void setPixels(byte *pixels, int width, int height, Graphics::PixelFormat format);
+
+ /**
+ * Draws the given string into the back buffer using the images stored in _font
+ */
+ virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor);
+ void writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2);
+
+ inline uint16 w() const { return _surface.w; }
+ inline uint16 h() const { return _surface.h; }
+ inline const byte *getPixels() const { return (const byte *)_surface.getPixels(); }
+ inline byte *getPixels() { return (byte *)_surface.getPixels(); }
+ inline byte *getBasePtr(int x, int y) { return (byte *)_surface.getBasePtr(x, y); }
+ inline const byte *getBasePtr(int x, int y) const { return (const byte *)_surface.getBasePtr(x, y); }
+ inline void hLine(int x, int y, int x2, uint32 color) { _surface.hLine(x, y, x2, color); }
+ inline void vLine(int x, int y, int y2, uint32 color) { _surface.vLine(x, y, y2, color); }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp
new file mode 100644
index 0000000000..a1a003d751
--- /dev/null
+++ b/engines/sherlock/talk.cpp
@@ -0,0 +1,1297 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/talk.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/screen.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_people.h"
+#include "sherlock/scalpel/scalpel_talk.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_talk.h"
+
+namespace Sherlock {
+
+SequenceEntry::SequenceEntry() {
+ _objNum = 0;
+ _frameNumber = 0;
+ _seqTo = 0;
+}
+
+/*----------------------------------------------------------------*/
+
+void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
+ int length;
+
+ length = s.readUint16LE();
+ for (int idx = 0; idx < length - 1; ++idx)
+ _statement += (char)s.readByte();
+ s.readByte(); // Null ending
+
+ length = s.readUint16LE();
+ for (int idx = 0; idx < length - 1; ++idx)
+ _reply += (char)s.readByte();
+ s.readByte(); // Null ending
+
+ length = s.readUint16LE();
+ for (int idx = 0; idx < length - 1; ++idx)
+ _linkFile += (char)s.readByte();
+ s.readByte(); // Null ending
+
+ length = s.readUint16LE();
+ for (int idx = 0; idx < length - 1; ++idx)
+ _voiceFile += (char)s.readByte();
+ s.readByte(); // Null ending
+
+ _required.resize(s.readByte());
+ _modified.resize(s.readByte());
+
+ // Read in flag required/modified data
+ for (uint idx = 0; idx < _required.size(); ++idx)
+ _required[idx] = s.readSint16LE();
+ for (uint idx = 0; idx < _modified.size(); ++idx)
+ _modified[idx] = s.readSint16LE();
+
+ _portraitSide = s.readByte();
+ _quotient = s.readUint16LE();
+ _journal = isRoseTattoo ? s.readByte() : 0;
+}
+
+/*----------------------------------------------------------------*/
+
+TalkHistoryEntry::TalkHistoryEntry() {
+ Common::fill(&_data[0], &_data[16], false);
+}
+
+/*----------------------------------------------------------------*/
+
+TalkSequence::TalkSequence() {
+ _obj = nullptr;
+ _frameNumber = 0;
+ _sequenceNumber = 0;
+ _seqStack = 0;
+ _seqTo = 0;
+ _seqCounter = _seqCounter2 = 0;
+}
+
+/*----------------------------------------------------------------*/
+
+Talk *Talk::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelTalk(vm);
+ else
+ return new Tattoo::TattooTalk(vm);
+}
+
+Talk::Talk(SherlockEngine *vm) : _vm(vm) {
+ _talkCounter = 0;
+ _talkToAbort = false;
+ _openTalkWindow = false;
+ _speaker = 0;
+ _talkIndex = 0;
+ _talkTo = 0;
+ _scriptSelect = 0;
+ _converseNum = -1;
+ _talkStealth = 0;
+ _talkToFlag = -1;
+ _moreTalkDown = _moreTalkUp = false;
+ _scriptMoreFlag = 0;
+ _scriptSaveIndex = -1;
+ _opcodes = nullptr;
+ _opcodeTable = nullptr;
+
+ _charCount = 0;
+ _line = 0;
+ _yp = 0;
+ _wait = 0;
+ _pauseFlag = false;
+ _seqCount = 0;
+ _scriptStart = _scriptEnd = nullptr;
+ _endStr = _noTextYet = false;
+
+ _talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500);
+}
+
+void Talk::talkTo(const Common::String &filename) {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ Journal &journal = *_vm->_journal;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+ Common::Rect savedBounds = screen.getDisplayBounds();
+ bool abortFlag = false;
+
+ if (filename.empty())
+ // No filename passed, so exit
+ return;
+
+ // If there any canimations currently running, or a portrait is being cleared,
+ // save the filename for later executing when the canimation is done
+ bool ongoingAnim = scene._canimShapes.size() > 0;
+ if (IS_ROSE_TATTOO) {
+ ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active();
+ }
+ if (ongoingAnim || people._clearingThePortrait) {
+ // Make sure we're not in the middle of a script
+ if (!_scriptMoreFlag) {
+ _scriptName = filename;
+ _scriptSaveIndex = 0;
+
+ // Flag the selection, since we don't yet know which statement yet
+ _scriptSelect = 100;
+ _scriptMoreFlag = 3;
+ }
+
+ return;
+ }
+
+ // Save the ui mode temporarily and switch to talk mode
+ MenuMode savedMode = ui._menuMode;
+ ui._menuMode = TALK_MODE;
+
+ // Turn on the Exit option
+ ui._endKeyActive = true;
+
+ if (people[HOLMES]._walkCount || (people[HOLMES]._walkTo.size() > 0 &&
+ (IS_SERRATED_SCALPEL || people._allowWalkAbort))) {
+ // Only interrupt if trying to do an action, and not just if player is walking around the scene
+ if (people._allowWalkAbort)
+ abortFlag = true;
+
+ people[HOLMES].gotoStand();
+ }
+
+ if (_talkToAbort)
+ return;
+
+ freeTalkVars();
+
+ // If any sequences have changed in the prior talk file, restore them
+ if (_savedSequences.size() > 0) {
+ for (uint idx = 0; idx < _savedSequences.size(); ++idx) {
+ SequenceEntry &ss = _savedSequences[idx];
+ for (uint idx2 = 0; idx2 < ss._sequences.size(); ++idx2)
+ scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2];
+
+ // Reset the object's frame to the beginning of the sequence
+ scene._bgShapes[ss._objNum]._frameNumber = 0;
+ }
+ }
+
+ while (!_sequenceStack.empty())
+ pullSequence();
+
+ if (IS_SERRATED_SCALPEL) {
+ // Restore any pressed button
+ if (!ui._windowOpen && savedMode != STD_MODE)
+ static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1));
+ } else {
+ static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
+ }
+
+ // Clear the ui counter so that anything displayed on the info line
+ // before the window was opened isn't cleared
+ ui._menuCounter = 0;
+
+ // Close any previous window before starting the talk
+ if (ui._windowOpen) {
+ switch (savedMode) {
+ case LOOK_MODE:
+ events.setCursor(ARROW);
+
+ if (ui._invLookFlag) {
+ screen.resetDisplayBounds();
+ ui.drawInterface(2);
+ }
+
+ ui.banishWindow();
+ ui._windowBounds.top = CONTROLS_Y1;
+ ui._temp = ui._oldTemp = ui._lookHelp = 0;
+ ui._menuMode = STD_MODE;
+ events._pressed = events._released = events._oldButtons = 0;
+ ui._invLookFlag = false;
+ break;
+
+ case TALK_MODE:
+ if (_speaker < SPEAKER_REMOVE)
+ people.clearTalking();
+ if (_talkCounter)
+ return;
+
+ // If we were in inventory mode looking at an object, restore the
+ // back buffers before closing the window, so we get the ui restored
+ // rather than the inventory again
+ if (ui._invLookFlag) {
+ screen.resetDisplayBounds();
+ ui.drawInterface(2);
+ ui._invLookFlag = ui._lookScriptFlag = false;
+ }
+
+ ui.banishWindow();
+ ui._windowBounds.top = CONTROLS_Y1;
+ abortFlag = true;
+ break;
+
+ case INV_MODE:
+ case USE_MODE:
+ case GIVE_MODE:
+ inv.freeInv();
+ if (ui._invLookFlag) {
+ screen.resetDisplayBounds();
+ ui.drawInterface(2);
+ ui._invLookFlag = ui._lookScriptFlag = false;
+ }
+
+ ui._infoFlag = true;
+ ui.clearInfo();
+ ui.banishWindow(false);
+ ui._key = -1;
+ break;
+
+ case FILES_MODE:
+ ui.banishWindow(true);
+ ui._windowBounds.top = CONTROLS_Y1;
+ abortFlag = true;
+ break;
+
+ case SETUP_MODE:
+ ui.banishWindow(true);
+ ui._windowBounds.top = CONTROLS_Y1;
+ ui._temp = ui._oldTemp = ui._lookHelp = ui._invLookFlag = false;
+ ui._menuMode = STD_MODE;
+ events._pressed = events._released = events._oldButtons = 0;
+ abortFlag = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ screen.resetDisplayBounds();
+ events._pressed = events._released = false;
+ loadTalkFile(filename);
+ ui._selector = ui._oldSelector = ui._key = ui._oldKey = -1;
+
+ // Find the first statement that has the correct flags
+ int select = -1;
+ for (uint idx = 0; idx < _statements.size() && select == -1; ++idx) {
+ if (_statements[idx]._talkMap == 0)
+ select = _talkIndex = idx;
+ }
+
+ // If there's a pending automatic selection to be made, then use it
+ if (_scriptMoreFlag && _scriptSelect != 100)
+ select = _scriptSelect;
+
+ if (select == -1)
+ error("Couldn't find statement to display");
+
+ // Add the statement into the journal and talk history
+ if (_talkTo != -1 && !_talkHistory[_converseNum][select])
+ journal.record(_converseNum, select, true);
+ _talkHistory[_converseNum][select] = true;
+
+ // Check if the talk file is meant to be a non-seen comment
+ if (filename.size() < 8 || filename[7] != '*') {
+ // Should we start in stealth mode?
+ if (_statements[select]._statement.hasPrefix("^")) {
+ _talkStealth = 2;
+ } else {
+ // Not in stealth mode, so bring up the ui window
+ _talkStealth = 0;
+ ++_talkToFlag;
+ events.setCursor(WAIT);
+
+ ui._windowBounds.top = CONTROLS_Y;
+ ui._infoFlag = true;
+ ui.clearInfo();
+ }
+
+ // Handle replies until there's no further linked file,
+ // or the link file isn't a reply first cnversation
+ while (!_vm->shouldQuit()) {
+ clearSequences();
+ _scriptSelect = select;
+ _speaker = _talkTo;
+
+ Statement &statement = _statements[select];
+ doScript(_statements[select]._reply);
+
+ if (_talkToAbort)
+ return;
+
+ if (!_talkStealth)
+ ui.clearWindow();
+
+ if (statement._modified.size() > 0) {
+ for (uint idx = 0; idx < statement._modified.size(); ++idx)
+ _vm->setFlags(statement._modified[idx]);
+
+ setTalkMap();
+ }
+
+ // Check for a linked file
+ if (!statement._linkFile.empty() && !_scriptMoreFlag) {
+ Common::String linkFilename = statement._linkFile;
+ freeTalkVars();
+ loadTalkFile(linkFilename);
+
+ // Scan for the first valid statement in the newly loaded file
+ select = -1;
+ for (uint idx = 0; idx < _statements.size(); ++idx) {
+ if (_statements[idx]._talkMap == 0) {
+ select = idx;
+ break;
+ }
+ }
+
+ if (_talkToFlag == 1)
+ pullSequence();
+
+ // Set the stealth mode for the new talk file
+ Statement &newStatement = _statements[select];
+ _talkStealth = newStatement._statement.hasPrefix("^") ? 2 : 0;
+
+ // If the new conversion is a reply first, then we don't need
+ // to display any choices, since the reply needs to be shown
+ if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) {
+ _talkIndex = select;
+ showTalk();
+
+ // Break out of loop now that we're waiting for player input
+ events.setCursor(ARROW);
+ break;
+ } else {
+ // Add the statement into the journal and talk history
+ if (_talkTo != -1 && !_talkHistory[_converseNum][select])
+ journal.record(_converseNum, select, true);
+ _talkHistory[_converseNum][select] = true;
+
+ }
+
+ ui._key = ui._oldKey = Scalpel::COMMANDS[TALK_MODE - 1];
+ ui._temp = ui._oldTemp = 0;
+ ui._menuMode = TALK_MODE;
+ _talkToFlag = 2;
+ } else {
+ freeTalkVars();
+
+ if (!ui._lookScriptFlag) {
+ ui.drawInterface(2);
+ ui._menuMode = STD_MODE;
+ ui._windowBounds.top = CONTROLS_Y1;
+
+ ui.banishWindow();
+ }
+
+ break;
+ }
+ }
+ }
+
+ _talkStealth = 0;
+ events._pressed = events._released = events._oldButtons = 0;
+ events.clearKeyboard();
+
+ if (savedBounds.bottom == SHERLOCK_SCREEN_HEIGHT)
+ screen.resetDisplayBounds();
+ else
+ screen.setDisplayBounds(savedBounds);
+
+ _talkToAbort = abortFlag;
+
+ // If a script was added to the script stack, restore state so that the
+ // previous script can continue
+ popStack();
+
+ if (IS_SERRATED_SCALPEL && filename == "Tube59c") {
+ // WORKAROUND: Original game bug causes the results of testing the powdery substance
+ // to disappear too quickly. Introduce a delay to allow it to be properly displayed
+ ui._menuCounter = 30;
+ }
+
+ events.setCursor(ARROW);
+}
+
+void Talk::talk(int objNum) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ UserInterface &ui = *_vm->_ui;
+
+ ui._windowBounds.top = CONTROLS_Y;
+ ui._infoFlag = true;
+ _speaker = SPEAKER_REMOVE;
+
+ Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name;
+ loadTalkFile(talkFilename);
+
+ // Find the first statement with the correct flags
+ int select = -1;
+ for (uint idx = 0; idx < _statements.size(); ++idx) {
+ if (_statements[idx]._talkMap == 0) {
+ select = idx;
+ break;
+ }
+ }
+ if (select == -1)
+ error("No entry matched all required flags");
+
+ // See if the statement is a stealth mode reply
+ Statement &statement = _statements[select];
+ if (statement._statement.hasPrefix("^")) {
+ clearSequences();
+
+ // Start talk in stealth mode
+ _talkStealth = 2;
+
+ talkTo(talkFilename);
+ } else if (statement._statement.hasPrefix("*")) {
+ // Character being spoken to will speak first
+ if (objNum > 1000) {
+ (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
+ } else {
+ Object &obj = scene._bgShapes[objNum];
+ clearSequences();
+ pushSequence(_talkTo);
+ setStillSeq(_talkTo);
+
+ events.setCursor(WAIT);
+ if (obj._lookPosition.y != 0)
+ // Need to walk to character first
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
+ events.setCursor(ARROW);
+ }
+
+ if (!_talkToAbort)
+ talkTo(talkFilename);
+ } else {
+ // Holmes will be speaking first
+ _talkToFlag = false;
+
+ if (objNum > 1000) {
+ (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC();
+ } else {
+ Object &obj = scene._bgShapes[objNum];
+ clearSequences();
+ pushSequence(_talkTo);
+ setStillSeq(_talkTo);
+
+ events.setCursor(WAIT);
+ if (obj._lookPosition.y != 0)
+ // Walk over to person to talk to
+ people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing);
+ events.setCursor(ARROW);
+ }
+
+ if (!_talkToAbort) {
+ // See if walking over triggered a conversation
+ if (_talkToFlag) {
+ if (_talkToFlag == 1) {
+ events.setCursor(ARROW);
+ // _sequenceStack._count = 1;
+ pullSequence();
+ }
+ } else {
+ _talkIndex = select;
+ showTalk();
+
+ // Break out of loop now that we're waiting for player input
+ events.setCursor(ARROW);
+ }
+
+ _talkToFlag = -1;
+ }
+ }
+}
+
+void Talk::freeTalkVars() {
+ _statements.clear();
+}
+
+void Talk::loadTalkFile(const Common::String &filename) {
+ People &people = *_vm->_people;
+ Resources &res = *_vm->_res;
+ Sound &sound = *_vm->_sound;
+
+ // Save a copy of the talk filename
+ _scriptName = filename;
+
+ // Check for an existing person being talked to
+ _talkTo = -1;
+ for (int idx = 0; idx < (int)people._characters.size(); ++idx) {
+ if (!scumm_strnicmp(filename.c_str(), people._characters[idx]._portrait, 4)) {
+ _talkTo = idx;
+ break;
+ }
+ }
+
+ const char *chP = strchr(filename.c_str(), '.');
+ Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" :
+ Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk";
+
+ // Open the talk file for reading
+ Common::SeekableReadStream *talkStream = res.load(talkFile);
+ _converseNum = res.resourceIndex();
+ talkStream->skip(2); // Skip talk file version num
+
+ _statements.resize(talkStream->readByte());
+ for (uint idx = 0; idx < _statements.size(); ++idx)
+ _statements[idx].load(*talkStream, IS_ROSE_TATTOO);
+
+ delete talkStream;
+
+ if (!sound._voices)
+ stripVoiceCommands();
+ setTalkMap();
+}
+
+void Talk::stripVoiceCommands() {
+ for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
+ Statement &statement = _statements[sIdx];
+
+ // Scan for an sound effect byte, which indicates to play a sound
+ for (uint idx = 0; idx < statement._reply.size(); ++idx) {
+ if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) {
+ // Replace instruction character with a space, and delete the
+ // rest of the name following it
+ statement._reply = Common::String(statement._reply.c_str(),
+ statement._reply.c_str() + idx) + " " +
+ Common::String(statement._reply.c_str() + 9);
+ }
+ }
+
+ // Ensure the last character of the reply is not a space from the prior
+ // conversion loop, to avoid any issues with the space ever causing a page
+ // wrap, and ending up displaying another empty page
+ while (statement._reply.lastChar() == ' ')
+ statement._reply.deleteLastChar();
+ }
+}
+
+void Talk::setTalkMap() {
+ int statementNum = 0;
+
+ for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) {
+ Statement &statement = _statements[sIdx];
+
+ // Set up talk map entry for the statement
+ bool valid = true;
+ for (uint idx = 0; idx < statement._required.size(); ++idx) {
+ if (!_vm->readFlags(statement._required[idx]))
+ valid = false;
+ }
+
+ statement._talkMap = valid ? statementNum++ : -1;
+ }
+}
+
+void Talk::clearSequences() {
+ _sequenceStack.clear();
+}
+
+void Talk::pullSequence() {
+ Scene &scene = *_vm->_scene;
+
+ if (_sequenceStack.empty() || IS_ROSE_TATTOO)
+ return;
+
+ SequenceEntry seq = _sequenceStack.pop();
+ if (seq._objNum != -1) {
+ Object &obj = scene._bgShapes[seq._objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to restore too few frames");
+ } else {
+ for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
+ obj._sequences[idx] = seq._sequences[idx];
+
+ obj._frameNumber = seq._frameNumber;
+ obj._seqTo = seq._seqTo;
+ }
+ }
+}
+
+void Talk::pushSequence(int speaker) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ // Only proceed if a speaker is specified
+ if (speaker == -1 || IS_ROSE_TATTOO)
+ return;
+
+ SequenceEntry seqEntry;
+ if (!speaker) {
+ seqEntry._objNum = -1;
+ } else {
+ seqEntry._objNum = people.findSpeaker(speaker);
+
+ if (seqEntry._objNum != -1) {
+ Object &obj = scene._bgShapes[seqEntry._objNum];
+ for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx)
+ seqEntry._sequences.push_back(obj._sequences[idx]);
+
+ seqEntry._frameNumber = obj._frameNumber;
+ seqEntry._seqTo = obj._seqTo;
+ }
+ }
+
+ _sequenceStack.push(seqEntry);
+ if (_scriptStack.size() >= 5)
+ error("script stack overflow");
+}
+
+void Talk::pushTalkSequence(Object *obj) {
+ // Check if the shape is already on the stack
+ for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ if (_talkSequenceStack[idx]._obj == obj)
+ return;
+ }
+
+ // Find a free slot and save the details in it
+ for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) {
+ TalkSequence &ts = _talkSequenceStack[idx];
+ if (ts._obj == nullptr) {
+ ts._obj = obj;
+ ts._frameNumber = obj->_frameNumber;
+ ts._sequenceNumber = obj->_sequenceNumber;
+ ts._seqStack = obj->_seqStack;
+ ts._seqTo = obj->_seqTo;
+ ts._seqCounter = obj->_seqCounter;
+ ts._seqCounter2 = obj->_seqCounter2;
+ return;
+ }
+ }
+
+ error("Ran out of talk sequence stack space");
+}
+
+void Talk::setStillSeq(int speaker) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ // Don't bother doing anything if no specific speaker is specified
+ if (speaker == -1)
+ return;
+
+ if (speaker) {
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1) {
+ Object &obj = scene._bgShapes[objNum];
+
+ if (obj._seqSize < MAX_TALK_SEQUENCES) {
+ warning("Tried to copy too few still frames");
+ } else {
+ for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) {
+ obj._sequences[idx] = people._characters[speaker]._stillSequences[idx];
+ if (idx > 0 && !people._characters[speaker]._talkSequences[idx] &&
+ !people._characters[speaker]._talkSequences[idx - 1])
+ break;
+ }
+
+ obj._frameNumber = 0;
+ obj._seqTo = 0;
+ }
+ }
+ }
+}
+
+void Talk::doScript(const Common::String &script) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ _savedSequences.clear();
+
+ _scriptStart = (const byte *)script.c_str();
+ _scriptEnd = _scriptStart + script.size();
+ const byte *str = _scriptStart;
+ _charCount = 0;
+ _line = 0;
+ _wait = 0;
+ _pauseFlag = false;
+ _seqCount = 0;
+ _noTextYet = true;
+ _endStr = false;
+ _openTalkWindow = false;
+
+ if (IS_SERRATED_SCALPEL)
+ _yp = CONTROLS_Y + 12;
+ else
+ _yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11;
+
+ if (IS_ROSE_TATTOO) {
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx];
+ p._savedNpcSequence = p._sequenceNumber;
+ p._savedNpcFrame = p._frameNumber;
+ }
+ }
+
+ if (_scriptMoreFlag) {
+ _scriptMoreFlag = 0;
+ str = _scriptStart + _scriptSaveIndex;
+ }
+
+ // Check if the script begins with a Stealh Mode Active command
+ if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) {
+ _talkStealth = 2;
+ _speaker |= SPEAKER_REMOVE;
+ } else {
+ pushSequence(_speaker);
+ if (IS_SERRATED_SCALPEL || ui._windowOpen)
+ ui.clearWindow();
+
+ // Need to switch speakers?
+ if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) {
+ _speaker = str[1] - 1;
+ str += IS_SERRATED_SCALPEL ? 2 : 3;
+
+ pullSequence();
+ pushSequence(_speaker);
+ people.setTalkSequence(_speaker);
+ } else {
+ people.setTalkSequence(_speaker);
+ }
+
+ if (IS_SERRATED_SCALPEL) {
+ // Assign portrait location?
+ if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) {
+ switch (str[1] & 15) {
+ case 1:
+ people._portraitSide = 20;
+ break;
+ case 2:
+ people._portraitSide = 220;
+ break;
+ case 3:
+ people._portraitSide = 120;
+ break;
+ default:
+ break;
+
+ }
+
+ if (str[1] > 15)
+ people._speakerFlip = true;
+ str += 2;
+ }
+
+ if (IS_SERRATED_SCALPEL) {
+ // Remove portrait?
+ if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) {
+ _speaker = -1;
+ } else {
+ // Nope, so set the first speaker
+ ((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker);
+ }
+ }
+ }
+ }
+
+ bool trigger3DOMovie = true;
+ uint16 subIndex = 1;
+
+ do {
+ Common::String tempString;
+ _wait = 0;
+
+ byte c = str[0];
+ if (!c) {
+ _endStr = true;
+ } else if (c == '{') {
+ // Start of comment, so skip over it
+ while (*str++ != '}')
+ ;
+ } else if (isOpcode(c)) {
+ // Handle control code
+ switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) {
+ case RET_EXIT:
+ return;
+ case RET_CONTINUE:
+ continue;
+ case OP_SWITCH_SPEAKER:
+ trigger3DOMovie = true;
+ break;
+ default:
+ break;
+ }
+
+ ++str;
+ } else {
+ // Handle drawing the talk interface with the text
+ talkInterface(str);
+ }
+
+ // Open window if it wasn't already open, and text has already been printed
+ if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) {
+ if (!ui._slideWindows) {
+ screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+ } else {
+ ui.summonWindow();
+ }
+
+ ui._windowOpen = true;
+ _openTalkWindow = false;
+ }
+
+ if ((_wait) && (trigger3DOMovie)) {
+ // Trigger to play 3DO movie
+ talk3DOMovieTrigger(subIndex);
+
+ trigger3DOMovie = false; // wait for next switch speaker opcode
+ subIndex++;
+ }
+
+ if (_wait)
+ // Handling pausing
+ talkWait(str);
+ } while (!_vm->shouldQuit() && !_endStr);
+
+ if (_wait != -1) {
+ for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) {
+ SequenceEntry &seq = _savedSequences[ssIndex];
+ Object &object = scene._bgShapes[seq._objNum];
+
+ for (uint idx = 0; idx < seq._sequences.size(); ++idx)
+ object._sequences[idx] = seq._sequences[idx];
+ object._frameNumber = seq._frameNumber;
+ object._seqTo = seq._seqTo;
+ }
+
+ pullSequence();
+
+ if (IS_SERRATED_SCALPEL) {
+ if (_speaker >= 0 && _speaker < SPEAKER_REMOVE)
+ people.clearTalking();
+ } else {
+ static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths();
+ }
+ }
+}
+
+int Talk::waitForMore(int delay) {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Sound &sound = *_vm->_sound;
+ UserInterface &ui = *_vm->_ui;
+ CursorId oldCursor = events.getCursor();
+ int key2 = 254;
+
+ // 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 == Common::KEYCODE_ESCAPE) {
+ if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) {
+ // Skip out of the introduction
+ _vm->setFlags(-76);
+ _vm->setFlags(396);
+ scene._goToScene = 1;
+ }
+ break;
+
+ } else if (Common::isPrint(keyState.ascii))
+ key2 = keyState.keycode;
+ }
+
+ if (_talkStealth) {
+ key2 = 254;
+ events._released = false;
+ }
+ }
+
+ // Count down the delay
+ if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth)
+ --delay;
+
+ // If 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;
+}
+
+bool Talk::isOpcode(byte checkCharacter) {
+ if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99)))
+ return false; // outside of range
+ if (_opcodeTable[checkCharacter - _opcodes[0]])
+ return true;
+ return false;
+}
+
+void Talk::popStack() {
+ if (!_scriptStack.empty()) {
+ ScriptStackEntry scriptEntry = _scriptStack.pop();
+ _scriptName = scriptEntry._name;
+ _scriptSaveIndex = scriptEntry._currentIndex;
+ _scriptSelect = scriptEntry._select;
+ _scriptMoreFlag = 1;
+ }
+}
+
+void Talk::synchronize(Serializer &s) {
+ for (uint idx = 0; idx < _talkHistory.size(); ++idx) {
+ TalkHistoryEntry &he = _talkHistory[idx];
+
+ for (int flag = 0; flag < 16; ++flag)
+ s.syncAsByte(he._data[flag]);
+ }
+}
+
+OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) {
+ Inventory &inv = *_vm->_inventory;
+ Common::String tempString;
+
+ ++str;
+ for (int idx = 0; idx < str[0]; ++idx)
+ tempString += str[idx + 1];
+ str += str[0];
+
+ inv.putNameInInventory(tempString);
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) {
+ Scene &scene = *_vm->_scene;
+ Common::String tempString;
+
+ // Get the name of the object to adjust
+ ++str;
+ for (int idx = 0; idx < (str[0] & 127); ++idx)
+ tempString += str[idx + 2];
+
+ // Scan for object
+ int objId = -1;
+ for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
+ if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name))
+ objId = idx;
+ }
+ if (objId == -1)
+ error("Could not find object %s to change", tempString.c_str());
+
+ // Should the script be overwritten?
+ if (str[0] > 0x80) {
+ // Save the current sequence
+ _savedSequences.push(SequenceEntry());
+ SequenceEntry &seqEntry = _savedSequences.top();
+ seqEntry._objNum = objId;
+ seqEntry._seqTo = scene._bgShapes[objId]._seqTo;
+ for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx)
+ seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]);
+ }
+
+ // Get number of bytes to change
+ _seqCount = str[1];
+ str += (str[0] & 127) + 2;
+
+ // 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::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 Talk::cmdDisableEndKey(const byte *&str) {
+ _vm->_ui->_endKeyActive = false;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) {
+ _vm->_ui->_endKeyActive = true;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) {
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdHolmesOff(const byte *&str) {
+ People &people = *_vm->_people;
+ people[HOLMES]._type = REMOVE;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdHolmesOn(const byte *&str) {
+ People &people = *_vm->_people;
+ people[HOLMES]._type = CHARACTER;
+
+ 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::cmdToggleObject(const byte *&str) {
+ Scene &scene = *_vm->_scene;
+ Common::String tempString;
+
+ ++str;
+ for (int idx = 0; idx < str[0]; ++idx)
+ tempString += str[idx + 1];
+
+ scene.toggleObject(tempString);
+ str += str[0];
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) {
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+
+ ++str;
+ CAnim &animation = scene._cAnim[str[0] - 1];
+ people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing);
+
+ return _talkToAbort ? RET_EXIT : RET_SUCCESS;
+}
+
+void Talk::talkWait(const byte *&str) {
+ if (!_pauseFlag && _charCount < 160)
+ _charCount = 160;
+
+ _wait = waitForMore(_charCount);
+ if (_wait == -1)
+ _endStr = true;
+
+ // If a key was pressed to finish the window, see if further voice files should be skipped
+ if (_wait >= 0 && _wait < 254) {
+ if (str[0] == _opcodes[OP_SFX_COMMAND])
+ str += 9;
+ }
+
+ _pauseFlag = false;
+}
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h
new file mode 100644
index 0000000000..ddb81f1b04
--- /dev/null
+++ b/engines/sherlock/talk.h
@@ -0,0 +1,381 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TALK_H
+#define SHERLOCK_TALK_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "common/stack.h"
+#include "sherlock/objects.h"
+#include "sherlock/saveload.h"
+
+namespace Sherlock {
+
+#define SPEAKER_REMOVE 0x80
+#define MAX_TALK_SEQUENCES 11
+#define TALK_SEQUENCE_STACK_SIZE 20
+
+enum {
+ OP_SWITCH_SPEAKER = 0,
+ OP_RUN_CANIMATION = 1,
+ OP_ASSIGN_PORTRAIT_LOCATION = 2,
+ OP_PAUSE = 3,
+ OP_REMOVE_PORTRAIT = 4,
+ OP_CLEAR_WINDOW = 5,
+ OP_ADJUST_OBJ_SEQUENCE = 6,
+ OP_WALK_TO_COORDS = 7,
+ OP_PAUSE_WITHOUT_CONTROL = 8,
+ OP_BANISH_WINDOW = 9,
+ OP_SUMMON_WINDOW = 10,
+ OP_SET_FLAG = 11,
+ OP_SFX_COMMAND = 12,
+ OP_TOGGLE_OBJECT = 13,
+ OP_STEALTH_MODE_ACTIVE = 14,
+ OP_IF_STATEMENT = 15,
+ OP_ELSE_STATEMENT = 16,
+ OP_END_IF_STATEMENT = 17,
+ OP_STEALTH_MODE_DEACTIVATE = 18,
+ OP_TURN_HOLMES_OFF = 19,
+ OP_TURN_HOLMES_ON = 20,
+ OP_GOTO_SCENE = 21,
+ OP_PLAY_PROLOGUE = 22,
+ OP_ADD_ITEM_TO_INVENTORY = 23,
+ OP_SET_OBJECT = 24,
+ OP_CALL_TALK_FILE = 25,
+ OP_MOVE_MOUSE = 26,
+ OP_DISPLAY_INFO_LINE = 27,
+ OP_CLEAR_INFO_LINE = 28,
+ OP_WALK_TO_CANIMATION = 29,
+ OP_REMOVE_ITEM_FROM_INVENTORY = 30,
+ OP_ENABLE_END_KEY = 31,
+ OP_DISABLE_END_KEY = 32,
+ OP_END_TEXT_WINDOW = 33,
+
+ OP_MOUSE_OFF_ON = 34,
+ OP_SET_WALK_CONTROL = 35,
+ OP_SET_TALK_SEQUENCE = 36,
+ OP_PLAY_SONG = 37,
+ OP_WALK_HOLMES_AND_NPC_TO_CANIM = 38,
+ OP_SET_NPC_PATH_DEST = 39,
+ OP_NEXT_SONG = 40,
+ OP_SET_NPC_PATH_PAUSE = 41,
+ OP_NEED_PASSWORD = 42,
+ OP_SET_SCENE_ENTRY_FLAG = 43,
+ OP_WALK_NPC_TO_CANIM = 44,
+ OP_WALK_NPC_TO_COORDS = 45,
+ OP_WALK_HOLMES_AND_NPC_TO_COORDS = 46,
+ OP_SET_NPC_TALK_FILE = 47,
+ OP_TURN_NPC_OFF = 48,
+ OP_TURN_NPC_ON = 49,
+ OP_NPC_DESC_ON_OFF = 50,
+ OP_NPC_PATH_PAUSE_TAKING_NOTES = 51,
+ OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 52,
+ OP_ENABLE_TALK_INTERRUPTS = 53,
+ OP_DISABLE_TALK_INTERRUPTS = 54,
+ OP_SET_NPC_INFO_LINE = 55,
+ OP_SET_NPC_POSITION = 56,
+ OP_NPC_PATH_LABEL = 57,
+ OP_PATH_GOTO_LABEL = 58,
+ OP_PATH_IF_FLAG_GOTO_LABEL = 59,
+ OP_NPC_WALK_GRAPHICS = 60,
+ OP_NPC_VERB = 61,
+ OP_NPC_VERB_CANIM = 62,
+ OP_NPC_VERB_SCRIPT = 63,
+ OP_RESTORE_PEOPLE_SEQUENCE = 64,
+ OP_NPC_VERB_TARGET = 65,
+ OP_TURN_SOUNDS_OFF = 66,
+ OP_NULL = 67
+};
+
+enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 };
+
+class SherlockEngine;
+class Talk;
+namespace Scalpel { class ScalpelUserInterface; }
+
+typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str);
+
+struct SequenceEntry {
+ int _objNum;
+ Common::Array<byte> _sequences;
+ int _frameNumber;
+ int _seqTo;
+
+ SequenceEntry();
+};
+
+struct ScriptStackEntry {
+ Common::String _name;
+ int _currentIndex;
+ int _select;
+};
+
+struct Statement {
+ Common::String _statement;
+ Common::String _reply;
+ Common::String _linkFile;
+ Common::String _voiceFile;
+ Common::Array<int> _required;
+ Common::Array<int> _modified;
+ int _portraitSide;
+ int _quotient;
+ int _talkMap;
+ Common::Rect _talkPos;
+ int _journal;
+
+ /**
+ * Load the data for a single statement within a talk file
+ */
+ void load(Common::SeekableReadStream &s, bool isRoseTattoo);
+};
+
+struct TalkHistoryEntry {
+ bool _data[16];
+
+ TalkHistoryEntry();
+ bool &operator[](int index) { return _data[index]; }
+};
+
+struct TalkSequence {
+ Object *_obj; // Pointer to the bgshape that these values go to
+ short _frameNumber; // Frame number in frame sequence to draw
+ short _sequenceNumber; // Start frame of sequences that are repeated
+ int _seqStack; // Allows gosubs to return to calling frame
+ int _seqTo; // Allows 1-5, 8-3 type sequences encoded
+ int _seqCounter; // How many times this sequence has been executed
+ int _seqCounter2;
+
+ TalkSequence();
+};
+
+
+class Talk {
+ friend class Scalpel::ScalpelUserInterface;
+private:
+ /**
+ * Remove any voice commands from a loaded statement list
+ */
+ void stripVoiceCommands();
+protected:
+ SherlockEngine *_vm;
+ OpcodeMethod *_opcodeTable;
+ Common::Stack<SequenceEntry> _savedSequences;
+ Common::Stack<SequenceEntry> _sequenceStack;
+ Common::Stack<ScriptStackEntry> _scriptStack;
+ Common::Array<TalkHistoryEntry> _talkHistory;
+ 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 cmdCallTalkFile(const byte *&str);
+ OpcodeReturn cmdDisableEndKey(const byte *&str);
+ OpcodeReturn cmdEnableEndKey(const byte *&str);
+ OpcodeReturn cmdEndTextWindow(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 cmdToggleObject(const byte *&str);
+ OpcodeReturn cmdWalkToCAnimation(const byte *&str);
+protected:
+ /**
+ * Checks, if a character is an opcode
+ */
+ bool isOpcode(byte checkCharacter);
+
+ /**
+ * Form a table of the display indexes for statements
+ */
+ void setTalkMap();
+
+ /**
+ * When the talk window has been displayed, waits a period of time proportional to
+ * the amount of text that's been displayed
+ */
+ int waitForMore(int delay);
+
+ /**
+ * Display the talk interface window
+ */
+ virtual void talkInterface(const byte *&str) = 0;
+
+ /**
+ * Pause when displaying a talk dialog on-screen
+ */
+ virtual void talkWait(const byte *&str);
+
+ /**
+ * Trigger to play a 3DO talk dialog movie
+ */
+ virtual void talk3DOMovieTrigger(int subIndex) {};
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk() = 0;
+public:
+ TalkSequence _talkSequenceStack[TALK_SEQUENCE_STACK_SIZE];
+ Common::Array<Statement> _statements;
+ bool _talkToAbort;
+ int _talkCounter;
+ int _talkTo;
+ int _scriptMoreFlag;
+ bool _openTalkWindow;
+ Common::String _scriptName;
+ bool _moreTalkUp, _moreTalkDown;
+ int _converseNum;
+ const byte *_opcodes;
+public:
+ static Talk *init(SherlockEngine *vm);
+ virtual ~Talk() {}
+
+ /**
+ * Return a given talk statement
+ */
+ Statement &operator[](int idx) { return _statements[idx]; }
+
+ /**
+ * Called whenever a conversation or item script needs to be run. For standard conversations,
+ * it opens up a description window similar to how 'talk' does, but shows a 'reply' directly
+ * instead of waiting for a statement option.
+ * @remarks It seems that at some point, all item scripts were set up to use this as well.
+ * In their case, the conversation display is simply suppressed, and control is passed on to
+ * doScript to implement whatever action is required.
+ */
+ void talkTo(const Common::String &filename);
+
+ /**
+ * 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 doScript(const Common::String &script);
+
+ /**
+ * Main method for handling conversations when a character to talk to has been
+ * selected. It will make Holmes walk to the person to talk to, draws the
+ * interface window for the conversation and passes on control to give the
+ * player a list of options to make a selection from
+ */
+ void talk(int objNum);
+
+ /**
+ * Clear loaded talk data
+ */
+ void freeTalkVars();
+
+ /**
+ * Opens the talk file 'talk.tlk' and searches the index for the specified
+ * conversation. If found, the data for that conversation is loaded
+ */
+ void loadTalkFile(const Common::String &filename);
+
+ /**
+ * Change the sequence of a background object corresponding to a given speaker.
+ * The new sequence will display the character as "listening"
+ */
+ void setStillSeq(int speaker);
+
+ /**
+ * Clears the stack of pending object sequences associated with speakers in the scene
+ */
+ void clearSequences();
+
+ /**
+ * Pulls a background object sequence from the sequence stack and restore's the
+ * object's sequence
+ */
+ void pullSequence();
+
+ /**
+ * Push the sequence of a background object that's an NPC that needs to be
+ * saved onto the sequence stack.
+ */
+ void pushSequence(int speaker);
+
+ /**
+ * Push a given shape's sequence data onto the Rose Tattoo talk sequence stack
+ */
+ void pushTalkSequence(Object *obj);
+
+ /**
+ * Returns true if the script stack is empty
+ */
+ bool isSequencesEmpty() const { return _scriptStack.empty(); }
+
+ /**
+ * Pops an entry off of the script stack
+ */
+ void popStack();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+
+ /**
+ * Draws the interface for conversation display
+ */
+ virtual void drawInterface() {}
+
+ /**
+ * Display a list of statements in a window at the bottom of the screen that the
+ * player can select from.
+ */
+ virtual bool displayTalk(bool slamIt) { return false; }
+
+ /**
+ * Prints a single conversation option in the interface window
+ */
+ virtual int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { return 0; }
+};
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo.cpp b/engines/sherlock/tattoo/tattoo.cpp
new file mode 100644
index 0000000000..90d2e5d958
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo.cpp
@@ -0,0 +1,576 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "engines/util.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_resources.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/widget_base.h"
+#include "sherlock/people.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) :
+ SherlockEngine(syst, gameDesc), _darts(this) {
+ _creditsActive = false;
+ _runningProlog = false;
+ _fastMode = false;
+ _allowFastMode = true;
+ _transparentMenus = true;
+ _creditSpeed = 4;
+}
+
+TattooEngine::~TattooEngine() {
+}
+
+void TattooEngine::showOpening() {
+ // No implementation - opening is done using in-game scenes
+}
+
+void TattooEngine::initialize() {
+ initGraphics(640, 480, true);
+
+ // Initialize the base engine
+ SherlockEngine::initialize();
+
+ // Initialise the global flags
+ _flags.resize(3200);
+ _flags[1] = _flags[4] = _flags[76] = true;
+ _runningProlog = true;
+
+ // Add some more files to the cache
+ _res->addToCache("walk.lib");
+
+ // Set up list of people
+ for (int idx = 0; idx < TATTOO_MAX_PEOPLE; ++idx) {
+ _people->_characters.push_back(PersonData(
+ getLanguage() == Common::FR_FRA ? FRENCH_NAMES[idx] : ENGLISH_NAMES[idx],
+ PORTRAITS[idx], nullptr, nullptr));
+ }
+
+ // Load the inventory
+ loadInventory();
+
+ // Starting scene
+ _scene->_goToScene = STARTING_INTRO_SCENE;
+
+ // Load an initial palette
+ loadInitialPalette();
+}
+
+void TattooEngine::startScene() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_ui;
+
+ switch (_scene->_goToScene) {
+ case 7:
+ case 8:
+ case 18:
+ case 53:
+ case 68:
+ // Load overlay mask(s) for the scene
+ ui._mask = new ImageFile(Common::String::format("res%02d.msk", _scene->_goToScene));
+ if (_scene->_goToScene == 8)
+ ui._mask1 = new ImageFile("res08a.msk");
+ else if (_scene->_goToScene == 18 || _scene->_goToScene == 68)
+ ui._mask1 = new ImageFile("res08a.msk");
+ break;
+
+ case OVERHEAD_MAP:
+ case OVERHEAD_MAP2:
+ // Show the map
+ _scene->_currentScene = OVERHEAD_MAP;
+ _scene->_goToScene = _map->show();
+
+ _people->_savedPos = Common::Point(-1, -1);
+ _people->_savedPos._facing = -1;
+ break;
+
+ case 101:
+ // Darts Board minigame
+ _darts.playDarts(GAME_CRICKET);
+ break;
+
+ case 102:
+ // Darts Board minigame
+ _darts.playDarts(GAME_301);
+ break;
+
+ case 103:
+ // Darts Board minigame
+ _darts.playDarts(GAME_501);
+ break;
+
+ default:
+ break;
+ }
+
+ _events->setCursor(ARROW);
+}
+
+void TattooEngine::loadInitialPalette() {
+ byte palette[768];
+ Common::SeekableReadStream *stream = _res->load("room.pal");
+ stream->read(palette, PALETTE_SIZE);
+ _screen->translatePalette(palette);
+ _screen->setPalette(palette);
+
+ delete stream;
+}
+
+void TattooEngine::loadInventory() {
+ Inventory &inv = *_inventory;
+
+ Common::String inv1 = _fixedText->getText(kFixedText_Inv1);
+ Common::String inv2 = _fixedText->getText(kFixedText_Inv2);
+ Common::String inv3 = _fixedText->getText(kFixedText_Inv3);
+ Common::String inv4 = _fixedText->getText(kFixedText_Inv4);
+ Common::String inv5 = _fixedText->getText(kFixedText_Inv5);
+ Common::String inv6 = _fixedText->getText(kFixedText_Inv6);
+ Common::String inv7 = _fixedText->getText(kFixedText_Inv7);
+ Common::String inv8 = _fixedText->getText(kFixedText_Inv8);
+ Common::String invDesc1 = _fixedText->getText(kFixedText_InvDesc1);
+ Common::String invDesc2 = _fixedText->getText(kFixedText_InvDesc2);
+ Common::String invDesc3 = _fixedText->getText(kFixedText_InvDesc3);
+ Common::String invDesc4 = _fixedText->getText(kFixedText_InvDesc4);
+ Common::String invDesc5 = _fixedText->getText(kFixedText_InvDesc5);
+ Common::String invDesc6 = _fixedText->getText(kFixedText_InvDesc6);
+ Common::String invDesc7 = _fixedText->getText(kFixedText_InvDesc7);
+ Common::String invDesc8 = _fixedText->getText(kFixedText_InvDesc8);
+ Common::String solve = _fixedText->getText(kFixedText_Solve);
+
+ // Initial inventory
+ inv._holdings = 5;
+ inv.push_back(InventoryItem(0, inv1, invDesc1, "_ITEM01A"));
+ inv.push_back(InventoryItem(0, inv2, invDesc2, "_ITEM02A"));
+ inv.push_back(InventoryItem(0, inv3, invDesc3, "_ITEM03A"));
+ inv.push_back(InventoryItem(0, inv4, invDesc4, "_ITEM04A"));
+ inv.push_back(InventoryItem(0, inv5, invDesc5, "_ITEM05A"));
+
+ // Hidden items
+ inv.push_back(InventoryItem(0, inv6, invDesc6, "_PAP212D", solve));
+ inv.push_back(InventoryItem(0, inv7, invDesc7, "_PAP212I"));
+ inv.push_back(InventoryItem(0, inv8, invDesc8, "_LANT02I"));
+}
+
+void TattooEngine::initCredits() {
+ Common::SeekableReadStream *stream = _res->load("credits.txt");
+ int spacing = _screen->fontHeight() * 2;
+ int yp = _screen->h();
+
+ _creditsActive = true;
+ _creditLines.clear();
+
+ while (stream->pos() < stream->size()) {
+ Common::String line = stream->readLine();
+
+ if (line.hasPrefix("Scroll Speed")) {
+ const char *p = line.c_str() + 12;
+ while ((*p < '0') || (*p > '9'))
+ p++;
+
+ _creditSpeed = atoi(p);
+ } else if (line.hasPrefix("Y Spacing")) {
+ const char *p = line.c_str() + 12;
+ while ((*p < '0') || (*p > '9'))
+ p++;
+
+ spacing = atoi(p) + _screen->fontHeight() + 1;
+ } else {
+ int width = _screen->stringWidth(line) + 2;
+
+ _creditLines.push_back(CreditLine(line, Common::Point((_screen->w() - width) / 2 + 1, yp), width));
+ yp += spacing;
+ }
+ }
+
+ // Post-processing for finding split lines
+ for (int l = 0; l < (int)_creditLines.size(); ++l) {
+ CreditLine &cl = _creditLines[l];
+ const char *p = strchr(cl._line.c_str(), '-');
+
+ if (p != nullptr && p[1] == '>') {
+ cl._line2 = Common::String(p + 3);
+ cl._line = Common::String(cl._line.c_str(), p);
+
+ int width = cl._width;
+ int width1 = _screen->stringWidth(cl._line);
+ int width2 = _screen->stringWidth(cl._line2);
+
+ int c = 1;
+ for (int l1 = l + 1; l1 < (int)_creditLines.size(); ++l1) {
+ if ((p = strchr(_creditLines[l1]._line.c_str(), '-')) != nullptr) {
+ if (p[1] == '>') {
+ Common::String line1 = Common::String(_creditLines[l1]._line.c_str(), p);
+ Common::String line2 = Common::String(p + 3);
+
+ width1 = MAX(width1, _screen->stringWidth(line1));
+
+ if (_screen->stringWidth(line2) > width2)
+ width2 = _screen->stringWidth(line2);
+ ++c;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ width = width1 + width2 + _screen->widestChar();
+ width1 += _screen->widestChar();
+
+ for (int l1 = l; l1 < l + c; ++l1) {
+ _creditLines[l1]._width = width;
+ _creditLines[l1]._xOffset = width1;
+ }
+
+ l += c - 1;
+ }
+ }
+
+ delete stream;
+}
+
+void TattooEngine::drawCredits() {
+ Common::Rect screenRect(0, 0, _screen->w(), _screen->h());
+ Surface &bb1 = _screen->_backBuffer1;
+
+ for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < _screen->h(); ++idx) {
+ if (screenRect.contains(_creditLines[idx]._position)) {
+ if (!_creditLines[idx]._line2.empty()) {
+ int x1 = _creditLines[idx]._position.x;
+ int x2 = x1 + _creditLines[idx]._xOffset;
+ const Common::String &line1 = _creditLines[idx]._line;
+ const Common::String &line2 = _creditLines[idx]._line2;
+
+ bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y - 1), 0);
+
+ bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y), 0);
+ bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y), 0);
+
+ bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y + 1), 0);
+
+ bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y), INFO_TOP);
+
+ bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y - 1), 0);
+
+ bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y), 0);
+ bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y), 0);
+
+ bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y + 1), 0);
+
+ bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y), INFO_TOP);
+ } else {
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1), 0);
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y - 1), 0);
+
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y), 0);
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y), 0);
+
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y + 1), 0);
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y + 1), 0);
+
+ bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y), INFO_TOP);
+ }
+ }
+ }
+}
+
+void TattooEngine::blitCredits() {
+ Common::Rect screenRect(0, -_creditSpeed, _screen->w(), _screen->h() + _creditSpeed);
+
+ for (uint idx = 0; idx < _creditLines.size(); ++idx) {
+ if (screenRect.contains(_creditLines[idx]._position)) {
+ Common::Rect r(_creditLines[idx]._width, _screen->fontHeight() + 2);
+ r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1);
+
+ _screen->slamRect(r);
+ }
+
+ _creditLines[idx]._position.y -= _creditSpeed;
+ }
+}
+
+void TattooEngine::eraseCredits() {
+ Common::Rect screenRect(0, -_creditSpeed, _screen->w(), _screen->h() + _creditSpeed);
+
+ for (uint idx = 0; idx < _creditLines.size(); ++idx) {
+ if (screenRect.contains(_creditLines[idx]._position)) {
+ Common::Rect r(_creditLines[idx]._width, _screen->fontHeight() + 3);
+ r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1 + _creditSpeed);
+
+ _screen->restoreBackground(r);
+ }
+ }
+
+ if (_creditLines[_creditLines.size() - 1]._position.y < -_creditSpeed) {
+ _creditLines.clear();
+ _creditsActive = false;
+ setFlags(!3000);
+ }
+}
+
+void TattooEngine::doHangManPuzzle() {
+ char answers[3][10];
+ Common::Point lines[3];
+ const char *solutions[3];
+ int numWide, spacing;
+ ImageFile *paper;
+ Common::Point cursorPos;
+ byte cursorColor = 254;
+ bool solved = false;
+ bool done = false;
+ bool flag = false;
+ size_t i = 0;
+
+ switch (getLanguage()) {
+ case Common::FR_FRA:
+ lines[0] = Common::Point(34, 210);
+ lines[1] = Common::Point(72, 242);
+ lines[2] = Common::Point(34, 276);
+ numWide = 8;
+ spacing = 19;
+ paper = new ImageFile("paperf.vgs");
+ break;
+
+ case Common::DE_DEU:
+ lines[0] = Common::Point(44, 73);
+ lines[1] = Common::Point(56, 169);
+ lines[2] = Common::Point(47, 256);
+ numWide = 7;
+ spacing = 19;
+ paper = new ImageFile("paperg.vgs");
+ break;
+
+ default:
+ // English
+ lines[0] = Common::Point(65, 84);
+ lines[1] = Common::Point(65, 159);
+ lines[2] = Common::Point(75, 234);
+ numWide = 5;
+ spacing = 20;
+ paper = new ImageFile("paper.vgs");
+ break;
+ }
+
+ ImageFrame &paperFrame = (*paper)[0];
+ Common::Rect paperBounds(paperFrame._width, paperFrame._height);
+ paperBounds.moveTo((_screen->w() - paperFrame._width) / 2, (_screen->h() - paperFrame._height) / 2);
+
+ for (int line = 0; line<3; ++line) {
+ lines[line].x += paperBounds.left;
+ lines[line].y += paperBounds.top;
+
+ for (i = 0; i <= (size_t)numWide; ++i)
+ answers[line][i] = 0;
+ }
+
+ _screen->_backBuffer1.blitFrom(paperFrame, Common::Point(paperBounds.left + _screen->_currentScroll.x, 0));
+
+ // If they have already solved the puzzle, put the answer on the graphic
+ if (readFlags(299)) {
+ for (int line = 0; line < 3; ++line) {
+ cursorPos.y = lines[line].y - _screen->fontHeight() - 2;
+
+ for (i = 0; i < strlen(solutions[line]); ++i) {
+ cursorPos.x = lines[line].x + 8 - _screen->widestChar() / 2 + i * spacing;
+ _screen->gPrint(Common::Point(cursorPos.x + _screen->widestChar() / 2 -
+ _screen->charWidth(solutions[line][i]) / 2, cursorPos.y), 0, "%c", solutions[line][i]);
+ }
+ }
+ }
+
+ _screen->slamRect(paperBounds);
+ cursorPos = Common::Point(lines[0].x + 8 - _screen->widestChar() / 2, lines[0].y - _screen->fontHeight() - 2);
+ int line = 0;
+
+ // If they have not solved the puzzle, let them solve it here
+ if (!readFlags(299)) {
+ do {
+ while (!_events->kbHit()) {
+ // See if a key or a mouse button is pressed
+ _events->pollEventsAndWait();
+ _events->setButtonState();
+
+ flag = !flag;
+ if (flag) {
+ _screen->_backBuffer1.fillRect(Common::Rect(cursorPos.x + _screen->_currentScroll.x, cursorPos.y,
+ cursorPos.x + _screen->widestChar() + _screen->_currentScroll.x - 1, cursorPos.y + _screen->fontHeight() - 1), cursorColor);
+ if (answers[line][i])
+ _screen->gPrint(Common::Point(cursorPos.x + _screen->widestChar() / 2 - _screen->charWidth(answers[line][i]) / 2,
+ cursorPos.y), 0, "%c", answers[line][i]);
+ _screen->slamArea(cursorPos.x, cursorPos.y, _screen->widestChar(), _screen->fontHeight());
+ } else {
+ _screen->setDisplayBounds(Common::Rect(cursorPos.x + _screen->_currentScroll.x, cursorPos.y,
+ cursorPos.x + _screen->widestChar() + _screen->_currentScroll.x, cursorPos.y + _screen->fontHeight()));
+ _screen->_backBuffer->blitFrom(paperFrame, Common::Point(paperBounds.left + _screen->_currentScroll.x, paperBounds.top));
+ _screen->resetDisplayBounds();
+
+ if (answers[line][i])
+ _screen->gPrint(Common::Point(cursorPos.x + _screen->widestChar() / 2 - _screen->charWidth(answers[line][i]) / 2,
+ cursorPos.y), 0, "%c", answers[line][i]);
+ _screen->slamArea(cursorPos.x, cursorPos.y, _screen->widestChar(), _screen->fontHeight());
+ }
+
+ if (!_events->kbHit())
+ _events->wait(2);
+ }
+
+ if (_events->kbHit()) {
+ Common::KeyState keyState = _events->getKey();
+
+ if (((toupper(keyState.ascii) >= 'A') && (toupper(keyState.ascii) <= 'Z')) ||
+ ((keyState.ascii >= 128) && ((keyState.ascii <= 168) || (keyState.ascii == 225)))) {
+ answers[line][i] = keyState.ascii;
+ keyState.keycode = Common::KEYCODE_RIGHT;
+ }
+
+ _screen->setDisplayBounds(Common::Rect(cursorPos.x + _screen->_currentScroll.x, cursorPos.y,
+ cursorPos.x + _screen->widestChar() + _screen->_currentScroll.x, cursorPos.y + _screen->fontHeight()));
+ _screen->_backBuffer->blitFrom(paperFrame, Common::Point(paperBounds.left + _screen->_currentScroll.x, paperBounds.top));
+ _screen->resetDisplayBounds();
+
+ if (answers[line][i])
+ _screen->gPrint(Common::Point(cursorPos.x + _screen->widestChar() / 2 - _screen->charWidth(answers[line][i]) / 2,
+ cursorPos.y), 0, "%c", answers[line][i]);
+ _screen->slamArea(cursorPos.x, cursorPos.y, _screen->widestChar(), _screen->fontHeight());
+
+ switch (keyState.keycode) {
+ case Common::KEYCODE_ESCAPE:
+ done = true;
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (line) {
+ line--;
+ if (i >= strlen(solutions[line]))
+ i = strlen(solutions[line]) - 1;
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (line < 2) {
+ ++line;
+ if (i >= strlen(solutions[line]))
+ i = strlen(solutions[line]) - 1;
+ }
+ break;
+
+ case Common::KEYCODE_BACKSPACE:
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (i)
+ --i;
+ else if (line) {
+ --line;
+
+ i = strlen(solutions[line]) - 1;
+ }
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE)
+ answers[line][i] = ' ';
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (i < strlen(solutions[line]) - 1)
+ i++;
+ else if (line < 2) {
+ ++line;
+ i = 0;
+ }
+ break;
+
+ case Common::KEYCODE_DELETE:
+ answers[line][i] = ' ';
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ cursorPos.x = lines[line].x + 8 - _screen->widestChar() / 2 + i * spacing;
+ cursorPos.y = lines[line].y - _screen->fontHeight() - 2;
+
+ // See if all of their anwers are correct
+ if (!scumm_stricmp(answers[0], solutions[0]) && !scumm_stricmp(answers[1], solutions[1]) &&
+ !scumm_stricmp(answers[2], solutions[2])) {
+ done = true;
+ solved = true;
+ }
+ } while (!done && !shouldQuit());
+ } else {
+ // They have already solved the puzzle, so just display the solution and wait for a mouse or key click
+ do {
+ _events->pollEventsAndWait();
+ _events->setButtonState();
+
+ if ((_events->kbHit()) || (_events->_released) || (_events->_rightReleased)) {
+ done = true;
+ _events->clearEvents();
+ }
+ } while (!done && !shouldQuit());
+ }
+
+ delete paper;
+ _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(paperBounds.left + _screen->_currentScroll.x, paperBounds.top),
+ Common::Rect(paperBounds.left + _screen->_currentScroll.x, paperBounds.top,
+ paperBounds.right + _screen->_currentScroll.x, paperBounds.bottom));
+ _scene->doBgAnim();
+
+ _screen->slamArea(paperBounds.left + _screen->_currentScroll.x, paperBounds.top,
+ paperBounds.width(), paperBounds.height());
+
+ // Don't call the talk files if the puzzle has already been solved
+ if (readFlags(299))
+ return;
+
+ // If they solved the puzzle correctly, set the solved flag and run the appropriate talk scripts
+ if (solved) {
+ _talk->talkTo("SLVE12S.TLK");
+ _talk->talkTo("WATS12X.TLK");
+ setFlags(299);
+ } else {
+ _talk->talkTo("HOLM12X.TLK");
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo.h b/engines/sherlock/tattoo/tattoo.h
new file mode 100644
index 0000000000..a9798dce41
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo.h
@@ -0,0 +1,127 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_H
+#define SHERLOCK_TATTOO_H
+
+#include "sherlock/sherlock.h"
+#include "sherlock/tattoo/tattoo_darts.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+enum {
+ INV_FOREGROUND = 167,
+ INV_BACKGROUND = 1,
+ INFO_FOREGROUND = 233,
+ INFO_BACKGROUND = 239,
+ INFO_TOP = 185,
+ INFO_MIDDLE = 186,
+ INFO_BOTTOM = 188,
+ MENU_BACKGROUND = 225,
+ COMMAND_FOREGROUND = 15,
+ COMMAND_HIGHLIGHTED = 254,
+ COMMAND_NULL = 193,
+ PEN_COLOR = 248,
+ PEN_HIGHLIGHT_COLOR = 129
+};
+
+enum {
+ FLAG_PLAYER_IS_HOLMES = 76,
+ FLAG_ALT_MAP_MUSIC = 525
+};
+
+struct CreditLine {
+ Common::Point _position;
+ int _xOffset;
+ int _width;
+ Common::String _line, _line2;
+
+ CreditLine(const Common::String &line, const Common::Point &pt, int width) :
+ _line(line), _position(pt), _width(width), _xOffset(0) {}
+};
+
+class TattooEngine : public SherlockEngine {
+private:
+ Darts _darts;
+ Common::Array<CreditLine> _creditLines;
+ int _creditSpeed;
+
+ /**
+ * Loads the initial palette for the game
+ */
+ void loadInitialPalette();
+
+ /**
+ * Load the initial inventory
+ */
+ void loadInventory();
+protected:
+ /**
+ * Initialize the engine
+ */
+ virtual void initialize();
+
+ virtual void showOpening();
+
+ /**
+ * Starting a scene within the game
+ */
+ virtual void startScene();
+public:
+ bool _creditsActive;
+ bool _runningProlog;
+ bool _fastMode, _allowFastMode;
+ bool _transparentMenus;
+public:
+ TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc);
+ virtual ~TattooEngine();
+
+ /**
+ * Initialize and load credit data for display
+ */
+ void initCredits();
+
+ /**
+ * Draw credits on the screen
+ */
+ void drawCredits();
+
+ /**
+ * Blit the drawn credits to the screen
+ */
+ void blitCredits();
+
+ /**
+ * Erase any area of the screen covered by credits
+ */
+ void eraseCredits();
+
+ void doHangManPuzzle();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_darts.cpp b/engines/sherlock/tattoo/tattoo_darts.cpp
new file mode 100644
index 0000000000..842320e270
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_darts.cpp
@@ -0,0 +1,967 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_darts.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+enum {
+ DART_COLOR_FORE = 5,
+ PLAYER_COLOR = 11
+};
+
+static const int STATUS_INFO_X = 430;
+static const int STATUS_INFO_Y = 50;
+static const int STATUS_INFO_WIDTH = 205;
+static const int STATUS_INFO_HEIGHT = 330;
+static const int STATUS2_INFO_X = 510;
+static const int STATUS2_X_ADD = STATUS2_INFO_X - STATUS_INFO_X;
+static const int DART_BAR_VX = 10;
+static const int DART_HEIGHT_Y = 121;
+static const int DART_BAR_SIZE = 150;
+static const int DARTBOARD_LEFT = 73;
+static const int DARTBOARD_TOP = 68;
+static const int DARTBOARD_WIDTH = 257;
+static const int DARTBOARD_HEIGHT = 256;
+static const int DARTBOARD_TOTALX = DARTBOARD_WIDTH * 120 / 100;
+static const int DARTBOARD_TOTALY = DARTBOARD_HEIGHT * 120 / 100;
+static const int DARTBOARD_TOTALTOP = DARTBOARD_TOP - DARTBOARD_WIDTH / 10;
+static const int DARTBOARD_TOTALLEFT = DARTBOARD_LEFT - DARTBOARD_HEIGHT / 10;
+static const int CRICKET_VALUE[7] = { 20, 19, 18, 17, 16, 15, 25 };
+
+Darts::Darts(SherlockEngine *vm) : _vm(vm) {
+ _gameType = GAME_301;
+ _hand1 = _hand2 = nullptr;
+ _dartGraphics = nullptr;
+ _dartsLeft = nullptr;
+ _dartMap = nullptr;
+ _dartBoard = nullptr;
+ Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0);
+ Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0);
+ _score1 = _score2 = 0;
+ _roundNum = 0;
+ _roundScore = 0;
+ _level = 0;
+ _oldDartButtons = false;
+ _handX = 0;
+ _compPlay = 1;
+}
+
+void Darts::playDarts(GameType gameType) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int oldFontType = screen.fontNumber();
+ int playerNum = 0;
+ int roundStart, score;
+ int lastDart;
+ int numHits = 0;
+ bool gameOver = false;
+ bool done = false;
+ const char *const NUM_HITS_STR[3] = { "a", FIXED(Double), FIXED(Triple) };
+
+ screen.setFont(7);
+ _spacing = screen.fontHeight() + 2;
+
+ while (!_vm->shouldQuit()) {
+ roundStart = score = (playerNum == 0) ? _score1 : _score2;
+
+ showNames(playerNum);
+ showStatus(playerNum);
+ _roundScore = 0;
+
+ for (int idx = 0; idx < 3; ++idx) {
+ if (_compPlay == 1)
+ lastDart = throwDart(idx + 1, playerNum * 2); /* Throw one dart */
+ else
+ if (_compPlay == 2)
+ lastDart = throwDart(idx + 1, playerNum + 1); /* Throw one dart */
+ else
+ lastDart = throwDart(idx + 1, 0); /* Throw one dart */
+
+ if (_gameType == GAME_301) {
+ score -= lastDart;
+ _roundScore += lastDart;
+ } else {
+ numHits = lastDart >> 16;
+ if (numHits == 0)
+ numHits = 1;
+ if (numHits > 3)
+ numHits = 3;
+
+ lastDart = lastDart & 0xffff;
+ updateCricketScore(playerNum, lastDart, numHits);
+ score = (playerNum == 0) ? _score1 : _score2;
+ }
+
+ if (_gameType == GAME_301) {
+ if (playerNum == 0)
+ _score1 = score;
+ else
+ _score2 = score;
+
+ if (score == 0)
+ // Someone won
+ gameOver = true;
+ } else {
+ // check for cricket game over
+ bool allClosed = true;
+ int nOtherScore;
+
+ for (int y = 0; y < 7; y++) {
+ if (_cricketScore[playerNum][y] < 3)
+ allClosed = false;
+ }
+
+ if (allClosed) {
+ nOtherScore = (playerNum == 0) ? _score2 : _score1;
+ if (score >= nOtherScore)
+ gameOver = true;
+ }
+ }
+
+ // Show scores
+ showStatus(playerNum);
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), idx + 1);
+
+ if (_gameType == GAME_301) {
+ if (_vm->getLanguage() == Common::FR_FRA)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s: %d", FIXED(Scored), FIXED(Points), lastDart);
+ else
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %d %s", FIXED(Scored), lastDart, FIXED(Points));
+ } else {
+ if (lastDart != 25)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s %d", FIXED(Hit), NUM_HITS_STR[numHits - 1], lastDart);
+ else
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0,
+ "%s %s %s", FIXED(Hit), NUM_HITS_STR[numHits - 1], FIXED(Bullseye));
+ }
+
+ if (score != 0 && playerNum == 0 && !gameOver)
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), 0,
+ "%s", FIXED(PressAKey));
+
+ if (gameOver) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3),
+ 0, "%s", FIXED(GameOver));
+ if (playerNum == 0) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0,
+ "%s %s", FIXED(Holmes), FIXED(Wins));
+ _vm->setFlagsDirect(531);
+ } else {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0,
+ "%s %s!", _opponent.c_str(), FIXED(Wins));
+ _vm->setFlagsDirect(530);
+ }
+
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 5), 0,
+ "%s", FIXED(PressAKey));
+
+ done = true;
+ idx = 10;
+ } else if (_gameType == GAME_301 && score < 0) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0,
+ "%s!", FIXED(Busted));
+
+ // End turn
+ idx = 10;
+ score = roundStart;
+ if (playerNum == 0)
+ _score1 = score;
+ else
+ _score2 = score;
+ }
+
+ // Clear keyboard events
+ events.clearEvents();
+
+ if ((playerNum == 0 && _compPlay == 1) || _compPlay == 0 || done) {
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ done = true;
+ idx = 10;
+ }
+ }
+ } else {
+ events.wait(20);
+ }
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.blitFrom(screen._backBuffer1);
+ }
+
+ playerNum ^= 1;
+ if (!playerNum)
+ ++_roundNum;
+
+ if (!done) {
+ screen._backBuffer2.blitFrom((*_dartBoard)[0], Common::Point(0, 0));
+ screen._backBuffer1.blitFrom(screen._backBuffer2);
+ screen.blitFrom(screen._backBuffer2);
+ }
+ }
+
+ closeDarts();
+ screen.fadeToBlack();
+ screen.setFont(oldFontType);
+}
+
+void Darts::initDarts() {
+ _dartInfo = Common::Rect(430, 50, 430 + 205, 50 + 330);
+
+ if (_gameType == GAME_CRICKET) {
+ _dartInfo = Common::Rect(430, 245, 430 + 205, 245 + 150);
+ }
+
+ Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0);
+ Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0);
+
+ switch (_gameType) {
+ case GAME_501:
+ _score1 = _score2 = 501;
+ _gameType = GAME_301;
+ break;
+
+ case GAME_301:
+ _score1 = _score2 = 301;
+ break;
+
+ default:
+ // Cricket
+ _score1 = _score2 = 0;
+ break;
+ }
+
+ _roundNum = 1;
+
+ if (_level == 9) {
+ // No computer players
+ _compPlay = 0;
+ _level = 0;
+ } else if (_level == 8) {
+ _level = _vm->getRandomNumber(3);
+ _compPlay = 2;
+ } else {
+ // Check for opponent flags
+ for (int idx = 0; idx < 4; ++idx) {
+ if (_vm->readFlags(314 + idx))
+ _level = idx;
+ }
+ }
+
+ _opponent = FIXED(Jock);
+}
+
+void Darts::loadDarts() {
+ Resources &res = *_vm->_res;
+ Screen &screen = *_vm->_screen;
+ byte palette[PALETTE_SIZE];
+
+ // Load images
+ _hand1 = new ImageFile("hand1.vgs");
+ _hand2 = new ImageFile("hand2.vgs");
+ _dartGraphics = new ImageFile("darts.vgs");
+ _dartsLeft = new ImageFile("DartsLft.vgs");
+ _dartMap = new ImageFile("DartMap.vgs");
+ _dartBoard = new ImageFile("DartBd.vgs");
+
+ // Load and set the palette
+ Common::SeekableReadStream *stream = res.load("DartBoard.pal");
+ stream->read(palette, PALETTE_SIZE);
+ screen.translatePalette(palette);
+ screen.setPalette(palette);
+ delete stream;
+
+ // Load the initial background
+ screen._backBuffer1.blitFrom((*_dartBoard)[0], Common::Point(0, 0));
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+ screen.blitFrom(screen._backBuffer1);
+}
+
+void Darts::closeDarts() {
+ delete _dartBoard;
+ delete _dartsLeft;
+ delete _dartGraphics;
+ delete _dartMap;
+ delete _hand1;
+ delete _hand2;
+}
+
+void Darts::showNames(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ byte color;
+
+ color = playerNum == 0 ? PLAYER_COLOR : DART_COLOR_FORE;
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", FIXED(Holmes));
+ screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+ screen.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+
+ color = playerNum == 1 ? PLAYER_COLOR : DART_COLOR_FORE;
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", _opponent.c_str());
+ screen._backBuffer1.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+ screen.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1,
+ STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color);
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+}
+
+void Darts::showStatus(int playerNum) {
+ Screen &screen = *_vm->_screen;
+ const char *const CRICKET_SCORE_NAME[7] = { "20", "19", "18", "17", "16", "15", FIXED(Bull) };
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
+ Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH,
+ STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10));
+ screen.print(Common::Point(STATUS_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score1);
+
+ screen.print(Common::Point(STATUS2_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score2);
+
+ int temp = (_gameType == GAME_CRICKET) ? STATUS_INFO_Y + 10 * _spacing + 5 : STATUS_INFO_Y + 55;
+ screen.print(Common::Point(STATUS_INFO_X, temp), 0, "%s: %d", FIXED(Round), _roundNum);
+
+ if (_gameType == GAME_301) {
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 75), 0, "%s: %d", FIXED(TurnTotal), _roundScore);
+ } else {
+ // Show cricket scores
+ for (int x = 0; x < 7; ++x) {
+ screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 40 + x * _spacing), 0, "%s:", CRICKET_SCORE_NAME[x]);
+
+ for (int y = 0; y < 2; ++y) {
+ switch (CRICKET_SCORE_NAME[y][x]) {
+ case 1:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "/");
+ break;
+ case 2:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "X");
+ break;
+ case 3:
+ screen.print(Common::Point(STATUS_INFO_X + 38 + y * STATUS2_X_ADD - 1, STATUS_INFO_Y + 40 + x * _spacing), 0, "X");
+ screen.print(Common::Point(STATUS_INFO_X + 37 + y * STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "O");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ screen.blitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10),
+ Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH,
+ STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10));
+}
+
+void Darts::erasePowerBars() {
+ Screen &screen = *_vm->_screen;
+
+ // Erase the old power bars and replace them with empty ones
+ screen.fillRect(Common::Rect(DART_BAR_VX, DART_HEIGHT_Y, DART_BAR_VX + 9, DART_HEIGHT_Y + DART_BAR_SIZE), 0);
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1));
+ screen.slamArea(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, 10, DART_BAR_SIZE + 2);
+}
+
+bool Darts::dartHit() {
+ Events &events = *_vm->_events;
+ events.pollEventsAndWait();
+
+ // Keyboard check
+ if (events.kbHit()) {
+ events.clearEvents();
+ return true;
+ }
+
+ bool result = events._pressed && !_oldDartButtons;
+ _oldDartButtons = events._pressed;
+ return result;
+}
+
+int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int x = 0;
+
+ events.clearEvents();
+ events.delay(100);
+
+ while (!_vm->shouldQuit()) {
+ if (x >= DART_BAR_SIZE)
+ break;
+
+ if ((goToPower - 1) == x)
+ break;
+ else if (goToPower == 0) {
+ if (dartHit())
+ break;
+ }
+
+ screen._backBuffer1.fillRect(Common::Rect(pt.x, pt.y + DART_BAR_SIZE - 1 - x,
+ pt.x + 8, pt.y + DART_BAR_SIZE - 2 - x), color);
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(pt.x - 1, pt.y - 1));
+ screen.slamArea(pt.x, pt.y + DART_BAR_SIZE - 1 - x, 8, 2);
+
+ if (!(x % 8))
+ events.wait(1);
+
+ x += 1;
+ }
+
+ return MIN(x * 100 / DART_BAR_SIZE, 100);
+}
+
+int Darts::drawHand(int goToPower, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ const int HAND_OFFSET[2] = { 72, 44 };
+ ImageFile *hands;
+ int hand;
+
+ goToPower = (goToPower * DARTBOARD_WIDTH) / 150;
+
+ if (!computer) {
+ hand = 0;
+ hands = _hand1;
+ } else {
+ hand = 1;
+ hands = _hand2;
+ }
+
+ _handSize.x = (*hands)[0]._offset.x + (*hands)[0]._width;
+ _handSize.y = (*hands)[0]._offset.y + (*hands)[0]._height;
+
+ // Clear keyboard buffer
+ events.clearEvents();
+ events.delay(100);
+
+ Common::Point pt(DARTBOARD_LEFT - HAND_OFFSET[hand], SHERLOCK_SCREEN_HEIGHT - _handSize.y);
+ int x = 0;
+
+ while (!_vm->shouldQuit()) {
+ if (x >= DARTBOARD_WIDTH)
+ break;
+
+ if ((goToPower - 1) == x)
+ break;
+ else if (goToPower == 0) {
+ if (dartHit())
+ break;
+ }
+
+ screen._backBuffer1.transBlitFrom((*hands)[0], pt);
+ screen.slamArea(pt.x - 1, pt.y, _handSize.x + 1, _handSize.y);
+ screen.restoreBackground(Common::Rect(pt.x, pt.y, pt.x + _handSize.x, pt.y + _handSize.y));
+
+ if (!(x % 8))
+ events.wait(1);
+
+ ++x;
+ ++pt.x;
+ }
+
+ _handX = pt.x - 1;
+
+ return MIN(x * 100 / DARTBOARD_WIDTH, 100);
+}
+
+Common::Point Darts::convertFromScreenToScoreCoords(const Common::Point &pt) const {
+ return Common::Point(CLIP((int)pt.x, 0, DARTBOARD_WIDTH), CLIP((int)pt.y, 0, DARTBOARD_HEIGHT));
+}
+
+int Darts::dartScore(const Common::Point &pt) {
+ Common::Point pos(pt.x - DARTBOARD_LEFT, pt.y - DARTBOARD_TOP);
+ if (pos.x < 0 || pos.y < 0)
+ return 0;
+ int score;
+
+ if (pos.x < DARTBOARD_WIDTH && pos.y < DARTBOARD_HEIGHT) {
+ pos = convertFromScreenToScoreCoords(pos);
+ score = *(const byte *)(*_dartMap)[0]._frame.getBasePtr(pos.x, pos.y);
+
+ if (_gameType == GAME_301) {
+ if (score >= 100) {
+ if (score <= 120)
+ // Hit a double
+ score = (score - 100) * 2;
+ else
+ // Hit a triple
+ score = (score - 120) * 3;
+ }
+ } else if (score >= 100) {
+ if (score >= 120)
+ // Hit a double
+ score = (2 << 16) + (score - 100);
+ else
+ // Hit a triple
+ score = (3 << 16) + (score - 120);
+ }
+ } else {
+ score = 0;
+ }
+
+ return score;
+}
+
+void Darts::drawDartThrow(const Common::Point &dartPos, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int cx, cy;
+ int handCy;
+ int drawX = 0, drawY = 0, oldDrawX = 0, oldDrawY = 0;
+ int xSize = 0, ySize = 0, oldxSize = 0, oldySize = 0;
+ int handOCx, handOCy;
+ int ocx, ocy;
+ int handOldxSize, handOldySize;
+ int delta = 9;
+ int dartNum;
+ int hddy;
+
+ // Draw the animation of the hand throwing the dart first
+ // See which hand animation to use
+ ImageFile &hands = !computer ? *_hand1 : *_hand2;
+ int numFrames = !computer ? 14 : 13;
+
+ ocx = ocy = handOCx = handOCy = 0;
+ oldxSize = oldySize = handOldxSize = handOldySize = 1;
+ cx = dartPos.x;
+ cy = SHERLOCK_SCREEN_HEIGHT - _handSize.y - 20;
+
+ hddy = (cy - dartPos.y) / (numFrames - 7);
+ hddy += 2;
+ hddy = hddy * 10 / 8;
+ if (dartPos.y > 275)
+ hddy += 3;
+
+ for (int idx = 0; idx < numFrames; ++idx) {
+ _handSize.x = hands[idx]._offset.x + hands[idx]._width;
+ _handSize.y = hands[idx]._offset.y + hands[idx]._height;
+ handCy = SHERLOCK_SCREEN_HEIGHT - _handSize.y;
+
+ screen._backBuffer1.transBlitFrom(hands[idx], Common::Point(_handX, handCy));
+ screen.slamArea(_handX, handCy, _handSize.x + 1, _handSize.y);
+ screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize);
+ screen.restoreBackground(Common::Rect(_handX, handCy, _handX + _handSize.x, handCy + _handSize.y));
+
+ handOCx = _handX;
+ handOCy = handCy;
+ handOldxSize = _handSize.x;
+ handOldySize = _handSize.y;
+
+ if (idx > 6) {
+ dartNum = idx - 6;
+ if (computer)
+ dartNum += 19;
+
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2;
+ ocy = drawY = cy - (*_dartGraphics)[dartNum]._height;
+
+ // Draw dart
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], dartPos);
+
+ if (drawX < 0) {
+ xSize += drawX;
+ if (xSize < 0)
+ xSize = 1;
+ drawX = 0;
+ }
+
+ if (drawY < 0) {
+ ySize += drawY;
+ if (ySize < 0)
+ ySize = 1;
+ drawY = 0;
+ }
+
+ // Flush the drawn dart to the screen
+ screen.slamArea(drawX, drawY, xSize, ySize);
+ if (oldDrawX != -1)
+ // Flush the erased dart area
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize));
+
+ oldDrawX = drawX;
+ oldDrawY = drawY;
+ oldxSize = xSize;
+ oldySize = ySize;
+
+ cy -= hddy;
+ }
+
+ events.wait(1);
+ }
+
+ // Clear the last little bit of the hand from the screen
+ screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize);
+
+ // Erase the old dart
+ if (oldDrawX != -1)
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize));
+
+ cx = dartPos.x;
+ cy = dartPos.y + 2;
+ oldDrawX = oldDrawY = -1;
+
+ for (int idx = 5; idx <= 23; ++idx) {
+ dartNum = idx - 4;
+ if (computer)
+ dartNum += 19;
+
+ if (idx < 14)
+ cy -= delta--;
+ else
+ if (idx == 14)
+ delta = 1;
+ if (idx > 14)
+ cy += delta++;
+
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2;
+ ocy = drawY = cy - (*_dartGraphics)[dartNum]._height;
+
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(drawX, drawY));
+
+ if (drawX < 0) {
+ xSize += drawX;
+ if (xSize < 0)
+ xSize = 1;
+ drawX = 0;
+ }
+
+ if (drawY < 0) {
+ ySize += drawY;
+ if (ySize < 0)
+ ySize = 1;
+ drawY = 0;
+ }
+
+ // flush the dart
+ screen.slamArea(drawX, drawY, xSize, ySize);
+ if (oldDrawX != -1)
+ screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize);
+
+ if (idx != 23)
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(drawX, drawY),
+ Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize)); // erase dart
+
+ events.wait(1);
+
+ oldDrawX = drawX;
+ oldDrawY = drawY;
+ oldxSize = xSize;
+ oldySize = ySize;
+ }
+
+ dartNum = 19;
+ if (computer)
+ dartNum += 19;
+ xSize = (*_dartGraphics)[dartNum]._width;
+ ySize = (*_dartGraphics)[dartNum]._height;
+
+ // Draw final dart on the board
+ screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy));
+ screen._backBuffer2.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy));
+ screen.slamArea(ocx, ocy, xSize, ySize);
+}
+
+int Darts::findNumberOnBoard(int aim, Common::Point &pt) {
+ ImageFrame &img = (*_dartMap)[0];
+
+ if ((aim > 20) && ((aim != 25) && (aim != 50))) {
+ if ((aim <= 40) && ((aim & 1) == 0)) {
+ aim /= 2;
+ aim += 100;
+ } else {
+ aim /= 3;
+ aim += 120;
+ }
+ }
+
+ bool done = false;
+ for (int y = 0; y < img._width && !done; ++y) {
+ for (int x = 0; x < img._height && !done; ++x) {
+ byte score = *(const byte *)img._frame.getBasePtr(x, y);
+
+ if (score == aim) {
+ // Found a match. Aim at non-double/triple numbers whenever possible.
+ // ie. Aim at 18 instead of triple 6 or double 9
+ done = true;
+
+ if (aim < 21) {
+ pt.x = x + 10;
+ pt.y = y + 10;
+
+ score = *(const byte *)img._frame.getBasePtr(x, y);
+ if (score != aim)
+ done = false;
+ } else {
+ // Aiming at double or triple
+ pt.x = x + 3;
+ pt.y = y + 3;
+ }
+ }
+ }
+ }
+
+ pt = convertFromScreenToScoreCoords(pt);
+
+ if (aim == 3)
+ pt.y += 30;
+ if (aim == 17)
+ pt.y += 10;
+
+ if (aim == 15) {
+ pt.y += 5;
+ pt.x += 5;
+ }
+
+ pt.y = DARTBOARD_HEIGHT - pt.y;
+ return done;
+}
+
+void Darts::getComputerNumber(int playerNum, Common::Point &targetPos) {
+ int score;
+ int aim = 0;
+ Common::Point pt;
+ bool done = false;
+ int cricketaimset = false;
+ bool shootBull = false;
+
+ score = (playerNum == 0) ? _score1 : _score2;
+
+ if (_gameType == GAME_301) {
+ // Try to hit number
+ aim = score;
+ if(score > 60)
+ shootBull = true;
+ } else {
+ if (_cricketScore[playerNum][6] < 3) {
+ // shoot at bull first
+ aim = CRICKET_VALUE[6];
+ cricketaimset = true;
+ } else {
+ // Now check and shoot in this order: 20,19,18,17,16,15
+ for (int idx = 0; idx < 7; ++idx) {
+ if (_cricketScore[playerNum][idx] < 3) {
+ aim = CRICKET_VALUE[idx];
+ cricketaimset = true;
+ break;
+ }
+ }
+ }
+
+ if (!cricketaimset) {
+ // Everything is closed
+ // just in case we don't get set in loop below, which should never happen
+ aim = 14;
+ for (int idx = 0; idx < 7; ++idx) {
+ if (_cricketScore[playerNum^1][idx] < 3) {
+ // Opponent has this open
+ aim = CRICKET_VALUE[idx];
+
+ if (idx == 6)
+ shootBull = true;
+ }
+ }
+ }
+ }
+
+ if (shootBull) {
+ // Aim at bulls eye
+ targetPos.x = targetPos.y = 75;
+
+ if (_level <= 1) {
+ if (_vm->getRandomNumber(1) == 1) {
+ targetPos.x += (_vm->getRandomNumber(20)-10);
+ targetPos.y += (_vm->getRandomNumber(20)-10);
+ }
+ }
+ } else {
+ // Loop in case number does not exist on board
+ do {
+ done = findNumberOnBoard(aim, pt);
+ --aim;
+ } while (!done);
+
+ pt.x += DARTBOARD_TOTALLEFT * 70 / 100;
+ pt.y += DARTBOARD_TOTALTOP * 70 / 100;
+
+ // old * 3/2
+ targetPos.x = pt.x * 100 / DARTBOARD_TOTALX * 3 / 2;
+ targetPos.y = pt.y * 100 / DARTBOARD_TOTALY * 3 / 2;
+ }
+
+ // the higher the level, the more accurate the throw
+ int v = _vm->getRandomNumber(9);
+ v += _level * 2;
+
+ if (v <= 2) {
+ targetPos.x += _vm->getRandomNumber(70) - 35;
+ targetPos.y += _vm->getRandomNumber(70) - 35;
+ } else if (v <= 4) {
+ targetPos.x += _vm->getRandomNumber(50) - 25;
+ targetPos.y += _vm->getRandomNumber(50) - 25;
+ } else if (v <= 6) {
+ targetPos.x += _vm->getRandomNumber(30) - 15;
+ targetPos.y += _vm->getRandomNumber(30) - 15;
+ } else if (v <= 8) {
+ targetPos.x += _vm->getRandomNumber(20) -10;
+ targetPos.y += _vm->getRandomNumber(20) -10;
+ } else if (v <= 10) {
+ targetPos.x += _vm->getRandomNumber(11) - 5;
+ targetPos.y += _vm->getRandomNumber(11) - 5;
+ }
+
+ if (targetPos.x < 1)
+ targetPos.x = 1;
+ if (targetPos.y < 1)
+ targetPos.y = 1;
+}
+
+int Darts::throwDart(int dartNum, int computer) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ int height;
+ int horiz;
+ Common::Point targetPos;
+ Common::String temp;
+
+ /* clear keyboard buffer */
+ events.clearEvents();
+
+ erasePowerBars();
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), dartNum);
+
+ drawDartsLeft(dartNum, computer);
+
+ if (!computer) {
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", FIXED(HitAKey));
+ screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, "%s", FIXED(ToStart));
+ }
+
+ if (!computer) {
+ // Wait for a hit
+ while (!dartHit())
+ ;
+ } else {
+ events.wait(1);
+ }
+
+ drawDartsLeft(dartNum + 1, computer);
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+ screen.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1),
+ Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1));
+
+ if (computer) {
+ getComputerNumber(computer - 1, targetPos);
+ } else {
+ // Keyboard control
+ targetPos = Common::Point(0, 0);
+ }
+
+ horiz = drawHand(targetPos.x, computer);
+ height = doPowerBar(Common::Point(DART_BAR_VX, DART_HEIGHT_Y), DART_COLOR_FORE, targetPos.y, 1);
+
+ // Invert height
+ height = 101 - height;
+
+ // Copy power bars to the secondary back buffer
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1),
+ Common::Rect(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, DART_BAR_VX - 1 + 10,
+ DART_HEIGHT_Y - 1 + DART_BAR_SIZE + 2));
+
+ Common::Point dartPos(DARTBOARD_TOTALLEFT + horiz*DARTBOARD_TOTALX / 100,
+ DARTBOARD_TOTALTOP + height * DARTBOARD_TOTALY / 100);
+
+ dartPos.x += 2 - _vm->getRandomNumber(4);
+ dartPos.y += 2 - _vm->getRandomNumber(4);
+
+ drawDartThrow(dartPos, computer);
+ return dartScore(dartPos);
+}
+
+void Darts::doCricketScoreHits(int player, int scoreIndex, int numHits) {
+ while (numHits--) {
+ if (_cricketScore[player][scoreIndex] < 3)
+ _cricketScore[player][scoreIndex]++;
+ else if (_cricketScore[player ^ 1][scoreIndex] < 3) {
+ if (player == 0)
+ _score1 += CRICKET_VALUE[scoreIndex];
+ else
+ _score2 += CRICKET_VALUE[scoreIndex];
+ }
+ }
+}
+
+void Darts::updateCricketScore(int player, int dartVal, int multiplier) {
+ if (dartVal < 15)
+ return;
+
+ if (dartVal <= 20)
+ doCricketScoreHits(player, 20 - dartVal, multiplier);
+ else if (dartVal == 25)
+ doCricketScoreHits(player, 6, multiplier);
+}
+
+void Darts::drawDartsLeft(int dartNum, int computer) {
+ Screen &screen = *_vm->_screen;
+ const int DART_X1[3] = { 391, 451, 507 };
+ const int DART_Y1[3] = { 373, 373, 373 };
+ const int DART_X2[3] = { 393, 441, 502 };
+ const int DART_Y2[3] = { 373, 373, 373 };
+
+ screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DART_X1[0], DART_Y1[0]),
+ Common::Rect(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ for (int idx = 2; idx >= dartNum - 1; --idx) {
+ if (computer)
+ screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx + 3], Common::Point(DART_X2[idx], DART_Y2[idx]));
+ else
+ screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx], Common::Point(DART_X1[idx], DART_Y1[idx]));
+ }
+
+ screen.slamArea(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH - DART_X1[0], SHERLOCK_SCREEN_HEIGHT - DART_Y1[0]);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_darts.h b/engines/sherlock/tattoo/tattoo_darts.h
new file mode 100644
index 0000000000..f65ec19d10
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_darts.h
@@ -0,0 +1,172 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_DARTS_H
+#define SHERLOCK_TATTOO_DARTS_H
+
+#include "common/scummsys.h"
+#include "sherlock/image_file.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+enum GameType { GAME_301, GAME_CRICKET, GAME_501 };
+
+class Darts {
+private:
+ SherlockEngine *_vm;
+ GameType _gameType;
+ ImageFile *_hand1, *_hand2;
+ ImageFile *_dartGraphics;
+ ImageFile *_dartsLeft;
+ ImageFile *_dartMap;
+ ImageFile *_dartBoard;
+ Common::Rect _dartInfo;
+ int _cricketScore[2][7];
+ int _score1, _score2;
+ int _roundNum;
+ int _roundScore;
+ int _level;
+ int _compPlay;
+ Common::String _opponent;
+ int _spacing;
+ bool _oldDartButtons;
+ int _handX;
+ Common::Point _handSize;
+
+ /**
+ * Initialize game variables
+ */
+ void initDarts();
+
+ /**
+ * Load dartboard graphics
+ */
+ void loadDarts();
+
+ /**
+ * Free loaded dart images
+ */
+ void closeDarts();
+
+ /**
+ * Show the player names
+ */
+ void showNames(int playerNum);
+
+ /**
+ * Show the current scores
+ */
+ void showStatus(int playerNum);
+
+ /**
+ * Erases the power bars
+ */
+ void erasePowerBars();
+
+ /**
+ * Returns true if a mouse button or key is pressed
+ */
+ bool dartHit();
+
+ /**
+ * Shows a power bar and increments it until a key or mouse button is pressed. If the bar
+ * reaches the end, it will also end. The reached power bar number is returned.
+ * @param pt Bar position
+ * @param color draw color
+ * @param goToPower If provided, input is ignored, and the bar is increased up to the specified level
+ * @param orientation 0=Horizontal, 1=Vertical
+ */
+ int doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation);
+
+ /**
+ * This is similar to doPowerBar, except it draws the player's hand moving across the
+ * bottom of the screen to indicate the positioning of the darts
+ */
+ int drawHand(int goToPower, int computer);
+
+ /**
+ * Converts a passed co-ordinates from screen co-ordinates to an offset within the dartboard
+ */
+ Common::Point convertFromScreenToScoreCoords(const Common::Point &pt) const;
+
+ /**
+ * Return the score a dart at the given position will get
+ */
+ int dartScore(const Common::Point &pt);
+
+ /**
+ * Draw a dart travelling to the board
+ */
+ void drawDartThrow(const Common::Point &dartPos, int computer);
+
+ /**
+ * Looks for the passed number on the dartboard. If it finds it, it will return
+ * the co-ordinates of the center of the number
+ */
+ int findNumberOnBoard(int aim, Common::Point &pt);
+
+ /**
+ * Calculates a position for the comptuer wants to throw, and then calculates where they
+ * actually did throw. The computer will not always hit what it's aiming it.
+ */
+ void getComputerNumber(int playerNum, Common::Point &targetPos);
+
+ /**
+ * Throw one dart. If computer is 1 or 2, the computer will throw the dart, and user input
+ * will be ignored.
+ * @param computer 1=1st computer player, 2=2nd computer player
+ */
+ int throwDart(int dartNum, int computer);
+
+ /**
+ * This will update the number of hits for the target score, as well as updating the
+ * score if it's closed
+ */
+ void doCricketScoreHits(int player, int scoreIndex, int numHits);
+
+ /**
+ * Updates the score based upon what the dart hit
+ */
+ void updateCricketScore(int player, int dartVal, int multiplier);
+
+ /**
+ * Draw the darts left
+ */
+ void drawDartsLeft(int dartNum, int computer);
+public:
+ Darts(SherlockEngine *vm);
+
+ /**
+ * Play the darts game
+ */
+ void playDarts(GameType gameType);
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_debugger.cpp b/engines/sherlock/tattoo/tattoo_debugger.cpp
new file mode 100644
index 0000000000..8d59b4c592
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_debugger.cpp
@@ -0,0 +1,35 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_debugger.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+TattooDebugger::TattooDebugger(SherlockEngine *vm) : Debugger(vm) {
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_debugger.h b/engines/sherlock/tattoo/tattoo_debugger.h
new file mode 100644
index 0000000000..e729262b11
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_debugger.h
@@ -0,0 +1,44 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_DEBUGGER_H
+#define SHERLOCK_TATTOO_DEBUGGER_H
+
+#include "sherlock/debugger.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+class TattooDebugger : public Debugger {
+public:
+ TattooDebugger(SherlockEngine *vm);
+ virtual ~TattooDebugger() {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif /* SHERLOCK_TATTOO_DEBUGGER_H */
diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.cpp b/engines/sherlock/tattoo/tattoo_fixed_text.cpp
new file mode 100644
index 0000000000..72c67570de
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_fixed_text.cpp
@@ -0,0 +1,107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/sherlock.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+static const char *const FIXED_TEXT_ENGLISH[] = {
+ "Money",
+ "Card",
+ "Tobacco",
+ "Timetable",
+ "Summons",
+ "Foolscap",
+ "Damp Paper",
+ "Bull's Eye",
+
+ "Money",
+ "Card",
+ "Tobacco",
+ "Timetable",
+ "Summons",
+ "Foolscap",
+ "Foolscap",
+ "Bull's Eye Lantern",
+
+ "Open",
+ "Look",
+ "Talk",
+ "Use",
+ "Journal",
+ "Inventory",
+ "Options",
+ "Solve",
+ "with",
+ "No effect...",
+ "This person has nothing to say at the moment",
+
+ "Close Journal",
+ "Search Journal",
+ "Save Journal",
+ "Abort Search",
+ "Search Backwards",
+ "Search Forwards",
+ "Text Not Found !",
+
+ "Holmes",
+ "Jock",
+ "Bull",
+ "Round",
+ "Turn Total",
+ "Dart",
+ "to start",
+ "Hit a key",
+ "Press a key",
+ "bullseye",
+ "GAME OVER",
+ "BUSTED",
+ "Wins",
+ "Scored",
+ "points",
+ "Hit",
+ "Double",
+ "Triple",
+
+ "Apply",
+ "Water",
+ "Heat"
+};
+
+TattooFixedText::TattooFixedText(SherlockEngine *vm) : FixedText(vm) {
+}
+
+const char *TattooFixedText::getText(int fixedTextId) {
+ return FIXED_TEXT_ENGLISH[fixedTextId];
+}
+
+const Common::String TattooFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) {
+ return Common::String();
+}
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.h b/engines/sherlock/tattoo/tattoo_fixed_text.h
new file mode 100644
index 0000000000..c26d787095
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_fixed_text.h
@@ -0,0 +1,114 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_FIXED_TEXT_H
+#define SHERLOCK_TATTOO_FIXED_TEXT_H
+
+#include "sherlock/fixed_text.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+enum FixedTextId {
+ kFixedText_Inv1,
+ kFixedText_Inv2,
+ kFixedText_Inv3,
+ kFixedText_Inv4,
+ kFixedText_Inv5,
+ kFixedText_Inv6,
+ kFixedText_Inv7,
+ kFixedText_Inv8,
+ kFixedText_InvDesc1,
+ kFixedText_InvDesc2,
+ kFixedText_InvDesc3,
+ kFixedText_InvDesc4,
+ kFixedText_InvDesc5,
+ kFixedText_InvDesc6,
+ kFixedText_InvDesc7,
+ kFixedText_InvDesc8,
+ kFixedText_Open,
+ kFixedText_Look,
+ kFixedText_Talk,
+ kFixedText_Use,
+ kFixedText_Journal,
+ kFixedText_Inventory,
+ kFixedText_Options,
+ kFixedText_Solve,
+ kFixedText_With,
+ kFixedText_NoEffect,
+ kFixedText_NothingToSay,
+
+ kFixedText_CloseJournal,
+ kFixedText_SearchJournal,
+ kFixedText_SaveJournal,
+ kFixedText_AbortSearch,
+ kFixedText_SearchBackwards,
+ kFixedText_SearchForwards,
+ kFixedText_TextNotFound,
+
+ kFixedText_Holmes,
+ kFixedText_Jock,
+ kFixedText_Bull,
+ kFixedText_Round,
+ kFixedText_TurnTotal,
+ kFixedText_Dart,
+ kFixedText_ToStart,
+ kFixedText_HitAKey,
+ kFixedText_PressAKey,
+ kFixedText_Bullseye,
+ kFixedText_GameOver,
+ kFixedText_Busted,
+ kFixedText_Wins,
+ kFixedText_Scored,
+ kFixedText_Points,
+ kFixedText_Hit,
+ kFixedText_Double,
+ kFixedText_Triple,
+
+ kFixedText_Apply,
+ kFixedText_Water,
+ kFixedText_Heat
+};
+
+class TattooFixedText: public FixedText {
+private:
+public:
+ TattooFixedText(SherlockEngine *vm);
+ virtual ~TattooFixedText() {}
+
+ /**
+ * Gets text
+ */
+ virtual const char *getText(int fixedTextId);
+
+ /**
+ * Get action message
+ */
+ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_inventory.cpp b/engines/sherlock/tattoo/tattoo_inventory.cpp
new file mode 100644
index 0000000000..6bd1822c10
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_inventory.cpp
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_inventory.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+TattooInventory::TattooInventory(SherlockEngine *vm) : Inventory(vm) {
+ _invShapes.resize(8);
+}
+
+TattooInventory::~TattooInventory() {
+}
+
+void TattooInventory::loadInv() {
+ // Exit if the inventory names are already loaded
+ if (_names.size() > 0)
+ return;
+
+ // Load the inventory names
+ Common::SeekableReadStream *stream = _vm->_res->load("invent.txt");
+
+ int count = stream->readByte();
+ char c;
+
+ for (int idx = 0; idx < count; ++idx) {
+ Common::String name;
+ while ((c = stream->readByte()) != 0)
+ name += c;
+
+ _names.push_back(name);
+ }
+
+ delete stream;
+
+ loadGraphics();
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_inventory.h b/engines/sherlock/tattoo/tattoo_inventory.h
new file mode 100644
index 0000000000..a18324b785
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_inventory.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_INVENTORY_H
+#define SHERLOCK_TATTOO_INVENTORY_H
+
+#include "sherlock/inventory.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+class TattooInventory : public Inventory {
+public:
+ TattooInventory(SherlockEngine *vm);
+ ~TattooInventory();
+
+ /**
+ * Load the list of names the inventory items correspond to, if not already loaded,
+ * and then calls loadGraphics to load the associated graphics
+ */
+ virtual void loadInv();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp
new file mode 100644
index 0000000000..6df5ee7458
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_journal.cpp
@@ -0,0 +1,900 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_journal.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define JOURNAL_BAR_WIDTH 450
+
+TattooJournal::TattooJournal(SherlockEngine *vm) : Journal(vm) {
+ _journalImages = nullptr;
+ _selector = _oldSelector = 0;
+ _wait = false;
+ _exitJournal = false;
+ _scrollingTimer = 0;
+ _savedIndex = _savedSub = _savedPage = 0;
+
+ loadLocations();
+}
+
+void TattooJournal::show() {
+ Events &events = *_vm->_events;
+ Resources &res = *_vm->_res;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ byte palette[PALETTE_SIZE];
+
+ // Load journal images
+ _journalImages = new ImageFile("journal.vgs");
+
+ // Load palette
+ Common::SeekableReadStream *stream = res.load("journal.pal");
+ stream->read(palette, PALETTE_SIZE);
+ screen.translatePalette(palette);
+ ui.setupBGArea(palette);
+
+ // Set screen to black, and set background
+ screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0));
+ screen.empty();
+ screen.setPalette(palette);
+
+ if (_journal.empty()) {
+ _up = _down = false;
+ } else {
+ drawJournal(0, 0);
+ }
+ drawControls(0);
+ screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ _exitJournal = false;
+ _scrollingTimer = 0;
+
+ do {
+ events.pollEventsAndWait();
+ events.setButtonState();
+ _wait = true;
+
+ handleKeyboardEvents();
+ highlightJournalControls(true);
+
+ handleButtons();
+
+ if (_wait)
+ events.wait(2);
+
+ } while (!_vm->shouldQuit() && !_exitJournal);
+
+ // Clear events
+ events.clearEvents();
+
+ // Free the images
+ delete _journalImages;
+}
+
+void TattooJournal::handleKeyboardEvents() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+
+ if (!events.kbHit())
+ return;
+
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_TAB && (keyState.flags & Common::KBD_SHIFT)) {
+ // Shift tab
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ // See if mouse is over any of the journal controls
+ _selector = -1;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over an option, move the mouse to that it points to the first option
+ if (_selector == -1) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2));
+ } else {
+ if (_selector == 0)
+ _selector = 2;
+ else
+ --_selector;
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_PAGEUP || keyState.keycode == Common::KEYCODE_KP9) {
+ // See if they have Shift held down to go forward 10 pages
+ if (keyState.flags & Common::KBD_SHIFT) {
+ if (_page > 1) {
+ // Scroll Up 10 pages if possible
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ } else {
+ if (_page > 1) {
+ // Scroll Up 1 page
+ drawJournal(1, LINES_PER_PAGE);
+ drawScrollBar();
+ show();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN || keyState.keycode == Common::KEYCODE_KP3) {
+ if (keyState.flags & Common::KBD_SHIFT) {
+ if (_down) {
+ // Scroll down 10 Pages
+ if (_page + 10 > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ } else {
+ if (_down) {
+ // Scroll down 1 page
+ drawJournal(2, LINES_PER_PAGE);
+ drawScrollBar();
+ show();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_HOME || keyState.keycode == Common::KEYCODE_KP7) {
+ // Scroll to start of journal
+ if (_page > 1) {
+ // Go to the beginning of the journal
+ _index = _sub = _up = _down = 0;
+ _page = 1;
+
+ drawFrame();
+ drawJournal(0, 0);
+
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+
+ } else if (keyState.keycode == Common::KEYCODE_END || keyState.keycode == Common::KEYCODE_KP1) {
+ // Scroll to end of journal
+ if (_down) {
+ // Go to the end of the journal
+ drawJournal(2, 100000);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ _wait = false;
+ }
+ } else if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_KP_ENTER) {
+ events._pressed = false;
+ events._released = true;
+ events._oldButtons = 0;
+ } else if (keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _exitJournal = true;
+ } else if (keyState.keycode == Common::KEYCODE_TAB) {
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCENE_HEIGHT - r.height());
+
+ // See if the mouse is over any of the journal controls
+ _selector = -1;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over any of the options, move the mouse so that it points to the first option
+ if (_selector == -1) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2));
+ } else {
+ if (_selector == 2)
+ _selector = 0;
+ else
+ ++_selector;
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+ }
+}
+
+void TattooJournal::handleButtons() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ uint32 frameCounter = events.getFrameCounter();
+
+ if (_selector != -1 && events._pressed) {
+ if (frameCounter >= _scrollingTimer) {
+ // Set next scrolling time
+ _scrollingTimer = frameCounter + 6;
+
+ // Handle different scrolling actions
+ switch (_selector) {
+ case 3:
+ // Scroll left (1 page back)
+ if (_page > 1) {
+ // Scroll Up
+ drawJournal(1, LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case 4:
+ // Page left (10 pages back)
+ if (_page > 1) {
+ // Scroll Up 10 Pages if possible
+ if (_page < 11)
+ drawJournal(1, (_page - 1) * LINES_PER_PAGE);
+ else
+ drawJournal(1, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ show();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case 5:
+ // Page right (10 pages ahead)
+ if (_down) {
+ // Scroll Down 10 Pages
+ if (_page + 10 > _maxPage)
+ drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE);
+ else
+ drawJournal(2, 10 * LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ case 6:
+ // Scroll right (1 Page Ahead)
+ if (_down) {
+ // Scroll Down
+ drawJournal(2, LINES_PER_PAGE);
+ drawScrollBar();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ _wait = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (events._released || events._rightReleased) {
+ _scrollingTimer = 0;
+
+ switch (_selector) {
+ case 0:
+ _exitJournal = true;
+ break;
+
+ case 1: {
+ // Search Journal
+ disableControls();
+
+ bool notFound = false;
+ int dir;
+
+ do {
+ if ((dir = getFindName(notFound)) != 0) {
+ _savedIndex = _index;
+ _savedSub = _sub;
+ _savedPage = _page;
+
+ if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0)
+ {
+ _index = _savedIndex;
+ _sub = _savedSub;
+ _page = _savedPage;
+
+ drawFrame();
+ drawJournal(0, 0);
+ notFound = true;
+ } else {
+ break;
+ }
+
+ highlightJournalControls(false);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ } else {
+ break;
+ }
+ } while (!_vm->shouldQuit());
+ break;
+ }
+
+ case 2:
+ // Print Journal - not implemented in ScummVM
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void TattooJournal::loadLocations() {
+ Resources &res = *_vm->_res;
+
+ _directory.clear();
+ _locations.clear();
+
+ Common::SeekableReadStream *dir = res.load("talk.lib");
+ dir->skip(4); // Skip header
+
+ // Get the numer of entries
+ _directory.resize(dir->readUint16LE());
+ dir->seek((_directory.size() + 1) * 8, SEEK_CUR);
+
+ // Read in each entry
+ char buffer[17];
+ for (uint idx = 0; idx < _directory.size(); ++idx) {
+ dir->read(buffer, 17);
+ buffer[16] = '\0';
+
+ _directory[idx] = Common::String(buffer);
+ }
+
+ delete dir;
+
+ // Load in the locations stored in journal.txt
+ Common::SeekableReadStream *loc = res.load("journal.txt");
+
+ // Initialize locations
+ _locations.resize(100);
+ for (int idx = 0; idx < 100; ++idx)
+ _locations[idx] = "No Description";
+
+ while (loc->pos() < loc->size()) {
+ // In Rose Tattoo, each location line starts with the location
+ // number, followed by a dot, some spaces and its description
+ // in quotes
+ Common::String line = loc->readLine();
+ Common::String locNumStr;
+ int locNum = 0;
+ int i = 0;
+ Common::String locDesc;
+
+ // Get the location
+ while (Common::isDigit(line[i])) {
+ locNumStr += line[i];
+ i++;
+ }
+ locNum = atoi(locNumStr.c_str());
+
+ // Skip the dot, spaces and initial quotation mark
+ while (line[i] == ' ' || line[i] == '.' || line[i] == '\"')
+ i++;
+
+ do {
+ locDesc += line[i];
+ i++;
+ } while (line[i] != '\"');
+
+ _locations[locNum] = locDesc;
+ }
+
+ delete loc;
+}
+
+void TattooJournal::drawFrame() {
+ Screen &screen = *_vm->_screen;
+
+ screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0));
+ drawControls(0);
+
+}
+
+void TattooJournal::drawControls(int mode) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ImageFile &images = *ui._interfaceImages;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, !mode ? (BUTTON_SIZE + screen.fontHeight() + 13) :
+ (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, !mode ? (SHERLOCK_SCREEN_HEIGHT - r.height()) :
+ (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ Common::Rect inner = r;
+ inner.grow(-3);
+
+ if (vm._transparentMenus)
+ ui.makeBGArea(inner);
+ else
+ screen._backBuffer1.fillRect(inner, MENU_BACKGROUND);
+
+ // Draw the four corners of the info box
+ screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height));
+ screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height));
+
+ // Draw the top of the info box
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM);
+
+ // Draw the bottom of the info box
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM);
+
+ // Draw the left side of the info box
+ screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP);
+ screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE);
+ screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM);
+
+ // Draw the right side of the info box
+ screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP);
+ screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE);
+ screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM);
+
+ // Draw the sides of the separator bar above the scroll bar
+ int yp = r.top + screen.fontHeight() + 7;
+ screen._backBuffer1.transBlitFrom(images[4], Common::Point(r.left, yp - 1));
+ screen._backBuffer1.transBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1));
+
+ // Draw the bar above the scroll bar
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP);
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp + 1, r.right - images[5]._width, INFO_MIDDLE);
+ screen._backBuffer1.hLine(r.left + images[4]._width, yp + 2, r.right - images[5]._width, INFO_BOTTOM);
+
+ if (mode != 2) {
+ // Draw the Bars separating the Journal Commands
+ int xp = r.right / 3;
+ for (int idx = 0; idx < 2; ++idx) {
+ screen._backBuffer1.transBlitFrom(images[6], Common::Point(xp - 2, r.top + 1));
+ screen._backBuffer1.transBlitFrom(images[7], Common::Point(xp - 2, yp - 1));
+
+ screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP);
+ screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE);
+ screen._backBuffer1.hLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM);
+ xp = r.right / 3 * 2;
+ }
+ }
+
+ int savedSelector = _oldSelector;
+ _oldSelector = 100;
+
+ switch (mode) {
+ case 0:
+ highlightJournalControls(false);
+ break;
+ case 1:
+ highlightSearchControls(false);
+ break;
+ default:
+ break;
+ }
+
+ _oldSelector = savedSelector;
+}
+
+void TattooJournal::highlightJournalControls(bool slamIt) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ // Calculate the Scroll Position Bar
+ int numPages = (_maxPage + LINES_PER_PAGE) / LINES_PER_PAGE;
+ int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages;
+ barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6);
+
+ int barX = (numPages <= 1) ? r.left + 3 + BUTTON_SIZE : (r.width() - BUTTON_SIZE * 2 - 6 - barWidth)
+ * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE;
+
+ // See if the mouse is over any of the Journal Controls
+ Common::Rect bounds(r.left, r.top, r.right - 3, r.top + screen.fontHeight() + 7);
+ _selector = -1;
+ if (bounds.contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ else if (events._pressed) {
+ if (Common::Rect(r.left, r.top + screen.fontHeight() + 10, r.left + BUTTON_SIZE, r.top +
+ screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos))
+ // Press on the Scroll Left button
+ _selector = 3;
+ else if (Common::Rect(r.left + BUTTON_SIZE + 3, r.top + screen.fontHeight() + 10,
+ r.left + BUTTON_SIZE + 3 + (barX - r.left - BUTTON_SIZE - 3), r.top + screen.fontHeight() +
+ 10 + BUTTON_SIZE).contains(mousePos))
+ // Press on the Page Left button
+ _selector = 4;
+ else if (Common::Rect(barX + barWidth, r.top + screen.fontHeight() + 10,
+ barX + barWidth + (r.right - BUTTON_SIZE - 3 - barX - barWidth),
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos))
+ // Press on the Page Right button
+ _selector = 5;
+ else if (Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos))
+ // Press of the Scroll Right button
+ _selector = 6;
+ }
+
+ // See if the Search was selected, but is not available
+ if (_journal.empty() && (_selector == 1 || _selector == 2))
+ _selector = -1;
+
+ if (_selector == 4 && _oldSelector == 5)
+ _selector = 5;
+ else if (_selector == 5 && _oldSelector == 4)
+ _selector = 4;
+
+ // See if they're pointing at a different control
+ if (_selector != _oldSelector) {
+ // Print the Journal commands
+ int xp = r.left + r.width() / 6;
+ byte color = (_selector == 0) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(CloseJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(CloseJournal));
+ xp += r.width() / 3;
+
+ if (!_journal.empty())
+ color = (_selector == 1) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+ else
+ color = INFO_BOTTOM;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SearchJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(SearchJournal));
+ xp += r.width() / 3;
+
+ color = INFO_BOTTOM;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SaveJournal)) / 2, r.top + 5),
+ color, "%s", FIXED(SaveJournal));
+
+ // Draw the horizontal scrollbar
+ drawScrollBar();
+
+ if (slamIt)
+ screen.slamRect(r);
+
+ _oldSelector = _selector;
+ }
+}
+
+void TattooJournal::highlightSearchControls(bool slamIt) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+ Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+ const char *SEARCH_COMMANDS[3] = { FIXED(AbortSearch), FIXED(SearchBackwards), FIXED(SearchForwards) };
+
+ // See if the mouse is over any of the Journal Controls
+ _selector = -1;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + 7 + screen.fontHeight()).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // See if they're pointing at a different control
+ if (_selector != _oldSelector) {
+ // Print the search commands
+ int xp = r.left + r.width() / 6;
+
+ for (int idx = 0; idx < 3; ++idx) {
+ byte color = (_selector == idx) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+ screen.gPrint(Common::Point(xp - screen.stringWidth(SEARCH_COMMANDS[idx]) / 2,
+ r.top + 5), color, "%s", SEARCH_COMMANDS[idx]);
+ xp += r.width() / 3;
+ }
+
+ if (slamIt)
+ screen.slamRect(r);
+
+ _oldSelector = _selector;
+ }
+}
+
+void TattooJournal::drawScrollBar() {
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ bool raised;
+ byte color;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+
+ // Calculate the Scroll Position Bar
+ int numPages = (_maxPage + LINES_PER_PAGE) / LINES_PER_PAGE;
+ int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages;
+ barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6);
+ int barX;
+ if (numPages <= 1) {
+ barX = r.left + 3 + BUTTON_SIZE;
+ } else {
+ barX = (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) *
+ (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE;
+ if (barX + BUTTON_SIZE > r.left + r.width() - BUTTON_SIZE - 3)
+ barX = r.right - BUTTON_SIZE * 2 - 3;
+ }
+
+ // Draw the scroll bar here
+ // Draw the Scroll Left button
+ raised = _selector != 3;
+ screen._backBuffer1.fillRect(Common::Rect(r.left, r.top + screen.fontHeight() + 12, r.left + BUTTON_SIZE,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.left + 3, r.top + screen.fontHeight() + 10, r.left + 3 + BUTTON_SIZE,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE), raised);
+
+ color = (_page > 1) ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ screen._backBuffer1.vLine(r.left + 1 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 2 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 3 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.left + 4 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color);
+
+ // Draw the Scroll Right button
+ raised = _selector != 6;
+ screen._backBuffer1.fillRect(Common::Rect(r.right - BUTTON_SIZE - 1, r.top + screen.fontHeight() + 12,
+ r.right - 5, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), raised);
+
+ color = _down ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ screen._backBuffer1.vLine(r.right - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 2 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 3 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color);
+ screen._backBuffer1.vLine(r.right - 4 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2,
+ r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color);
+
+ // Draw the scroll bar
+ screen._backBuffer1.fillRect(Common::Rect(barX + 2, r.top + screen.fontHeight() + 12, barX + barWidth - 3,
+ r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE);
+ ui.drawDialogRect(screen._backBuffer1, Common::Rect(barX, r.top + screen.fontHeight() + 10, barX + barWidth,
+ r.top + screen.fontHeight() + 10 + BUTTON_SIZE), true);
+}
+
+void TattooJournal::disableControls() {
+ Screen &screen = *_vm->_screen;
+ Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_HEIGHT - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height());
+ const char *JOURNAL_COMMANDS[3] = { FIXED(CloseJournal), FIXED(SearchJournal), FIXED(SaveJournal) };
+
+ // Print the Journal commands
+ int xp = r.left + r.width() / 6;
+ for (int idx = 0; idx < 2; ++idx) {
+ screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5),
+ INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]);
+
+ xp += r.width() / 3;
+ }
+
+ screen.slamRect(r);
+}
+
+int TattooJournal::getFindName(bool printError) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ int result = 0;
+ int done = 0;
+ Common::String name;
+ int cursorX, cursorY;
+ bool flag = false;
+
+ Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ // Set the cursors Y position
+ cursorY = r.top + screen.fontHeight() + 12;
+
+ drawControls(1);
+
+ // Backup the area under the text entry
+ Surface bgSurface(r.width() - 6, screen.fontHeight());
+ bgSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY,
+ r.right - 3, cursorY + screen.fontHeight()));
+
+ if (printError) {
+ screen.gPrint(Common::Point(0, cursorY), INFO_TOP, "%s", FIXED(TextNotFound));
+ } else {
+ // If there was a name already entered, copy it to name and display it
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str());
+ name = _find;
+ }
+ }
+
+ screen.slamRect(r);
+
+ if (printError) {
+ // Pause to allow error to be shown
+ int timer = 0;
+
+ do {
+ events.pollEvents();
+ events.setButtonState();
+
+ ++timer;
+ events.wait(2);
+ } while (!_vm->shouldQuit() && !events.kbHit() && !events._released && !events._rightReleased && timer < 40);
+
+ events.clearEvents();
+
+ // Restore the text background
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left, cursorY));
+
+ // If there was a name already entered, copy it to name and display it
+ if (!_find.empty()) {
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str());
+ name = _find;
+ }
+
+ screen.slamArea(r.left + 3, cursorY, r.width() - 6, screen.fontHeight());
+ }
+
+ // Set the cursors X position
+ cursorX = r.left + screen.widestChar() + 3 + screen.stringWidth(name);
+
+ do {
+ events._released = events._rightReleased = false;
+
+ while (!events.kbHit() && !events._released && !events._rightReleased) {
+ if (talk._talkToAbort)
+ return 0;
+
+ // See if a key or a mouse button is pressed
+ events.pollEventsAndWait();
+ events.setButtonState();
+
+ // Handle blinking cursor
+ flag = !flag;
+ if (flag) {
+ // Draw cursor
+ screen._backBuffer1.fillRect(Common::Rect(cursorX, cursorY, cursorX + 7, cursorY + 8), COMMAND_HIGHLIGHTED);
+ screen.slamArea(cursorX, cursorY, 8, 9);
+ } else {
+ // Erase cursor by restoring background and writing current text
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY));
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str());
+ screen.slamArea(r.left + 3, r.top, r.width() - 3, screen.fontHeight());
+ }
+
+ highlightSearchControls(true);
+
+ events.wait(2);
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+ Common::Point mousePos = events.mousePos();
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE && !name.empty()) {
+ cursorX -= screen.charWidth(name.lastChar());
+ name.deleteLastChar();
+ }
+
+ if (keyState.keycode == Common::KEYCODE_RETURN)
+ done = 1;
+
+ else if (keyState.keycode == Common::KEYCODE_ESCAPE)
+ done = -1;
+
+ if (keyState.keycode == Common::KEYCODE_TAB) {
+ r = Common::Rect(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13);
+ r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2);
+
+ // See if the mouse is over any of the journal controls
+ _selector = -1;
+ if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos))
+ _selector = (mousePos.x - r.left) / (r.width() / 3);
+
+ // If the mouse is not over any of the options, move the mouse so that it points to the first option
+ if (_selector == -1) {
+ events.warpMouse(Common::Point(r.left + r.width() / 3, r.top + screen.fontHeight() + 2));
+ } else {
+ if (keyState.keycode & Common::KBD_SHIFT) {
+ if (_selector == 0)
+ _selector = 2;
+ else
+ --_selector;
+ } else {
+ if (_selector == 2)
+ _selector = 0;
+ else
+ ++_selector;
+ }
+
+ events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y));
+ }
+ }
+
+ if (keyState.ascii && keyState.ascii != '@' && name.size() < 50) {
+ if ((cursorX + screen.charWidth(keyState.ascii)) < (r.right - screen.widestChar() * 3)) {
+ cursorX += screen.charWidth(keyState.ascii);
+ name += toupper(keyState.ascii);
+ }
+ }
+
+ // Redraw the text
+ screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY));
+ screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED,
+ "%s", name.c_str());
+ screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight());
+ }
+
+ if (events._released || events._rightReleased) {
+ switch (_selector)
+ {
+ case 0:
+ done = -1;
+ break;
+ case 1:
+ done = 2;
+ break;
+ case 2:
+ done = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ } while (!done);
+
+ if (done != -1) {
+ _find = name;
+ result = done;
+ } else {
+ result = 0;
+ }
+
+ drawFrame();
+ drawJournal(0, 0);
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ return result;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_journal.h b/engines/sherlock/tattoo/tattoo_journal.h
new file mode 100644
index 0000000000..5e5cfda8c2
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_journal.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#ifndef SHERLOCK_TATTOO_JOURNAL_H
+#define SHERLOCK_TATTOO_JOURNAL_H
+
+#include "sherlock/journal.h"
+#include "sherlock/image_file.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+class TattooJournal : public Journal {
+private:
+ ImageFile *_journalImages;
+ int _selector, _oldSelector;
+ bool _wait;
+ bool _exitJournal;
+ uint32 _scrollingTimer;
+ int _savedIndex, _savedSub, _savedPage;
+
+ /**
+ * Load the list of journal locations
+ */
+ void loadLocations();
+
+ /**
+ * Displays the controls used by the journal
+ * @param mode 0: Normal journal buttons, 1: Search interface
+ */
+ void drawControls(int mode);
+
+ /**
+ * Draw the journal controls used by the journal
+ */
+ void highlightJournalControls(bool slamIt);
+
+ /**
+ * Draw the journal controls used in search mode
+ */
+ void highlightSearchControls(bool slamIt);
+
+ void drawScrollBar();
+
+ /**
+ * Check for and handle any pending keyboard events
+ */
+ void handleKeyboardEvents();
+
+ /**
+ * Handle mouse presses on interface buttons
+ */
+ void handleButtons();
+
+ /**
+ * Disable the journal controls
+ */
+ void disableControls();
+
+ /**
+ * Get in a name to search through the journal for
+ */
+ int getFindName(bool printError);
+public:
+ TattooJournal(SherlockEngine *vm);
+ virtual ~TattooJournal() {}
+
+ /**
+ * Show the journal
+ */
+ void show();
+public:
+ /**
+ * Draw the journal background, frame, and interface buttons
+ */
+ virtual void drawFrame();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_map.cpp b/engines/sherlock/tattoo/tattoo_map.cpp
new file mode 100644
index 0000000000..21abf7d3c0
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_map.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_map.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define MAP_NAME_COLOR 131
+#define CLOSEUP_STEPS 30
+#define SCROLL_SPEED 16
+
+/*-------------------------------------------------------------------------*/
+
+void MapEntry::clear() {
+ _iconNum = -1;
+ _description = "";
+}
+
+/*-------------------------------------------------------------------------*/
+
+TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) {
+ _iconImages = nullptr;
+ _bgFound = _oldBgFound = 0;
+
+ loadData();
+}
+
+int TattooMap::show() {
+ Debugger &debugger = *_vm->_debugger;
+ Events &events = *_vm->_events;
+ Music &music = *_vm->_music;
+ Resources &res = *_vm->_res;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ int result = 0;
+
+ // Check if we need to keep track of how many times player has been to the map
+ for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) {
+ SceneTripEntry &entry = scene._sceneTripCounters[idx];
+
+ if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) {
+ if (--entry._numTimes == 0) {
+ _vm->setFlagsDirect(entry._flag);
+ scene._sceneTripCounters.remove_at(idx);
+ }
+ }
+ }
+
+ if (music._midiOption) {
+ // See if Holmes or Watson is the active character
+ Common::String song;
+ if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES))
+ // Player is Holmes
+ song = "Cue9";
+ else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC))
+ song = "Cue8";
+ else
+ song = "Cue7";
+
+ if (music.loadSong(song)) {
+ music.setMIDIVolume(music._musicVolume);
+ if (music._musicOn)
+ music.startSong();
+ }
+ }
+
+ screen.initPaletteFade(1364485);
+
+ // Load the custom mouse cursors for the map
+ ImageFile cursors("omouse.vgs");
+ events.setCursor(cursors[0]._frame);
+ events.warpMouse(Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2));
+
+ // Load the data for the map
+ _iconImages = new ImageFile("mapicons.vgs");
+ loadData();
+
+ // Load the palette
+ Common::SeekableReadStream *stream = res.load("map.pal");
+ stream->read(screen._cMap, PALETTE_SIZE);
+ screen.translatePalette(screen._cMap);
+ delete stream;
+
+ // Load the map image and draw it to the back buffer
+ ImageFile *map = new ImageFile("map.vgs");
+ screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
+ screen._backBuffer1.blitFrom((*map)[0], Common::Point(0, 0));
+ delete map;
+
+ screen.clear();
+ screen.setPalette(screen._cMap);
+ drawMapIcons();
+
+ // Copy the map drawn in the back buffer to the secondary back buffer
+ screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2);
+ screen._backBuffer2.blitFrom(screen._backBuffer1);
+
+ // Display the built map to the screen
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ // Set initial scroll position
+ _targetScroll = _bigPos;
+ screen._currentScroll = Common::Point(-1, -1);
+
+ do {
+ // Allow for event processing and get the current mouse position
+ events.pollEventsAndWait();
+ events.setButtonState();
+ Common::Point mousePos = events.screenMousePos();
+
+ if (debugger._showAllLocations == LOC_REFRESH) {
+ drawMapIcons();
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH);
+ }
+
+ checkMapNames(true);
+
+ if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6))
+ _targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6);
+ if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6))
+ _targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6);
+ if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6))
+ _targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6);
+ if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6))
+ _targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6);
+
+ if (_targetScroll.x < 0)
+ _targetScroll.x = 0;
+ if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.w())
+ _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH;
+ if (_targetScroll.y < 0)
+ _targetScroll.y = 0;
+ if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.h())
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+
+ // Check the keyboard
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ switch (keyState.keycode) {
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ _targetScroll.x = 0;
+ _targetScroll.y = 0;
+ break;
+
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH;
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ _targetScroll.y -= SHERLOCK_SCREEN_HEIGHT;
+ if (_targetScroll.y < 0)
+ _targetScroll.y = 0;
+ break;
+
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ _targetScroll.y += SHERLOCK_SCREEN_HEIGHT;
+ if (_targetScroll.y > (screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT))
+ _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT;
+ break;
+
+ case Common::KEYCODE_SPACE:
+ events._pressed = false;
+ events._oldButtons = 0;
+ events._released = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ // Handle any scrolling of the map
+ if (screen._currentScroll != _targetScroll) {
+ // If there is a Text description being displayed, restore the area under it
+ _mapTooltip.erase();
+
+ screen._currentScroll = _targetScroll;
+
+ checkMapNames(false);
+ screen.slamArea(_targetScroll.x, _targetScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ }
+
+ // Handling if a location has been clicked on
+ if (events._released && _bgFound != -1) {
+ // If there is a Text description being displayed, restore the area under it
+ _mapTooltip.erase();
+
+ // Save the current scroll position on the map
+ _bigPos = screen._currentScroll;
+
+ showCloseUp(_bgFound);
+ result = _bgFound + 1;
+ }
+ } while (!result && !_vm->shouldQuit());
+
+ music.stopMusic();
+ events.clearEvents();
+ _mapTooltip.banishWindow();
+
+ // Reset the back buffers back to standard size
+ screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ return result;
+}
+
+void TattooMap::loadData() {
+ Resources &res = *_vm->_res;
+ char c;
+
+ Common::SeekableReadStream *stream = res.load("map.txt");
+
+ _data.resize(100);
+ for (uint idx = 0; idx < _data.size(); ++idx)
+ _data[idx].clear();
+
+ do
+ {
+ // Find the start of the number
+ do {
+ c = stream->readByte();
+ if (stream->pos() >= stream->size())
+ return;
+ } while (c < '0' || c > '9');
+
+ // Get the scene number
+ Common::String locStr;
+ locStr += c;
+ while ((c = stream->readByte()) != '.')
+ locStr += c;
+ MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1];
+
+ // Get the location name
+ while (stream->readByte() != '"')
+ ;
+
+ while ((c = stream->readByte()) != '"')
+ mapEntry._description += c;
+
+ // Find the ( specifying the (X,Y) position of the Icon
+ while (stream->readByte() != '(')
+ ;
+
+ // Get the X Position of the icon
+ Common::String numStr;
+ while ((c = stream->readByte()) != ',')
+ numStr += c;
+ mapEntry.x = atoi(numStr.c_str());
+
+ // Get the Y position of the icon
+ numStr = "";
+ while ((c = stream->readByte()) != ')')
+ numStr += c;
+ mapEntry.y = atoi(numStr.c_str());
+
+ // Find and get the location's icon number
+ while (stream->readByte() != '#')
+ ;
+
+ Common::String iconStr;
+ while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r')
+ iconStr += c;
+
+ mapEntry._iconNum = atoi(iconStr.c_str()) - 1;
+ } while (stream->pos() < stream->size());
+
+ delete stream;
+}
+
+void TattooMap::drawMapIcons() {
+ Debugger &debugger = *_vm->_debugger;
+ Screen &screen = *_vm->_screen;
+
+ for (uint idx = 0; idx < _data.size(); ++idx) {
+ if (debugger._showAllLocations != LOC_DISABLED)
+ _vm->setFlagsDirect(idx + 1);
+
+ if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
+ MapEntry &mapEntry = _data[idx];
+ ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
+ screen._backBuffer1.transBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2,
+ mapEntry.y - img._height / 2));
+ }
+ }
+
+ if (debugger._showAllLocations == LOC_REFRESH)
+ debugger._showAllLocations = LOC_ALL;
+}
+
+void TattooMap::checkMapNames(bool slamIt) {
+ Events &events = *_vm->_events;
+ Common::Point mapPos = events.mousePos();
+
+ // See if the mouse is pointing at any of the map locations
+ _bgFound = -1;
+
+ for (uint idx = 0; idx < _data.size(); ++idx) {
+ if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) {
+ MapEntry &mapEntry = _data[idx];
+ ImageFrame &img = (*_iconImages)[mapEntry._iconNum];
+ Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2,
+ mapEntry.x + img._width / 2, mapEntry.y + img._height / 2);
+
+ if (r.contains(mapPos)) {
+ _bgFound = idx;
+ break;
+ }
+ }
+ }
+
+ // Handle updating the tooltip
+ if (_bgFound != _oldBgFound) {
+ if (_bgFound == -1) {
+ _mapTooltip.setText("");
+ } else {
+ const Common::String &desc = _data[_bgFound]._description;
+ _mapTooltip.setText(desc);
+ }
+
+ _oldBgFound = _bgFound;
+ }
+
+ _mapTooltip.handleEvents();
+ if (slamIt)
+ _mapTooltip.draw();
+}
+
+void TattooMap::restoreArea(const Common::Rect &bounds) {
+ Screen &screen = *_vm->_screen;
+
+ Common::Rect r = bounds;
+ r.clip(Common::Rect(0, 0, screen._backBuffer1.w(), screen._backBuffer1.h()));
+
+ if (!r.isEmpty())
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r);
+}
+
+void TattooMap::showCloseUp(int closeUpNum) {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+
+ // Reset scroll position
+ screen._currentScroll = Common::Point(0, 0);
+
+ // Get the closeup images
+ Common::String fname = Common::String::format("res%02d.vgs", closeUpNum + 1);
+ ImageFile pic(fname);
+
+ Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100);
+ Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS,
+ (SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS);
+ Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1);
+ int size = 64;
+ int n = 256;
+ int deltaVal = 512;
+ bool minimize = false;
+ int scaleVal, newSize;
+
+ do {
+ scaleVal = n;
+ newSize = pic[0].sDrawXSize(n);
+
+ if (newSize > size) {
+ if (minimize)
+ deltaVal /= 2;
+ n += deltaVal;
+ } else {
+ minimize = true;
+ deltaVal /= 2;
+ n -= deltaVal;
+ if (n < 1)
+ n = 1;
+ }
+ } while (deltaVal && size != newSize);
+
+ int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS;
+
+ for (int step = 0; step < CLOSEUP_STEPS; ++step) {
+ Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal));
+ Common::Point pt(closeUp.x / 100 - picSize.x / 2, closeUp.y / 100 - picSize.y / 2);
+
+ restoreArea(oldBounds);
+ screen._backBuffer1.transBlitFrom(pic[0], pt, false, 0, scaleVal);
+
+ screen.slamRect(oldBounds);
+ screen.slamArea(pt.x, pt.y, picSize.x, picSize.y);
+
+ oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1);
+ closeUp += delta;
+ scaleVal += deltaScale;
+
+ events.wait(1);
+ }
+
+ // Handle final drawing of closeup
+ // TODO: Handle scrolling
+ Common::Rect r(SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2, SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2,
+ SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width,
+ SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height);
+
+ restoreArea(oldBounds);
+ screen._backBuffer1.transBlitFrom(pic[0], Common::Point(r.left, r.top));
+ screen.slamRect(oldBounds);
+ screen.slamRect(r);
+ events.wait(2);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_map.h b/engines/sherlock/tattoo/tattoo_map.h
new file mode 100644
index 0000000000..1c7173bdb0
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_map.h
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_MAP_H
+#define SHERLOCK_TATTOO_MAP_H
+
+#include "common/scummsys.h"
+#include "sherlock/map.h"
+#include "sherlock/resources.h"
+#include "sherlock/surface.h"
+#include "sherlock/tattoo/widget_tooltip.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+struct MapEntry : Common::Point {
+ int _iconNum;
+ Common::String _description;
+
+ MapEntry() : Common::Point(), _iconNum(-1) {}
+ MapEntry(int posX, int posY, int iconNum) : Common::Point(posX, posY), _iconNum(iconNum) {}
+ void clear();
+};
+
+class TattooMap : public Map {
+private:
+ Common::Array<MapEntry> _data;
+ ImageFile *_iconImages;
+ int _bgFound, _oldBgFound;
+ WidgetMapTooltip _mapTooltip;
+ Common::Point _targetScroll;
+
+ /**
+ * Load data needed for the map
+ */
+ void loadData();
+
+ /**
+ * Draws all available location icons onto the back buffer
+ */
+ void drawMapIcons();
+
+ /**
+ * Draws the location names of whatever the mouse moves over on the map
+ */
+ void checkMapNames(bool slamIt);
+
+ /**
+ * Restores an area of the map background
+ */
+ void restoreArea(const Common::Rect &bounds);
+
+ /**
+ * This will load a specified close up and zoom it up to the middle of the screen
+ */
+ void showCloseUp(int closeUpNum);
+public:
+ TattooMap(SherlockEngine *vm);
+ virtual ~TattooMap() {}
+
+ /**
+ * Show the map
+ */
+ virtual int show();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_people.cpp b/engines/sherlock/tattoo/tattoo_people.cpp
new file mode 100644
index 0000000000..7aaa0a082c
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_people.cpp
@@ -0,0 +1,1432 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_talk.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define FACING_PLAYER 16
+#define NUM_ADJUSTED_WALKS 21
+
+struct AdjustWalk {
+ char _vgsName[9];
+ int _xAdjust;
+ int _flipXAdjust;
+ int _yAdjust;
+} ;
+
+static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = {
+ { "TUPRIGHT", -7, -19, 6 },
+ { "TRIGHT", 8, -14, 0 },
+ { "TDOWNRG", 14, -12, 0 },
+ { "TWUPRIGH", 12, 4, 2 },
+ { "TWRIGHT", 31, -14, 0 },
+ { "TWDOWNRG", 6, -24, 0 },
+ { "HTUPRIGH", 2, -20, 0 },
+ { "HTRIGHT", 28, -20, 0 },
+ { "HTDOWNRG", 8, -2, 0 },
+ { "GTUPRIGH", 4, -12, 0 },
+ { "GTRIGHT", 12, -16, 0 },
+ { "GTDOWNRG", 10, -18, 0 },
+ { "JTUPRIGH", 8, -10, 0 },
+ { "JTRIGHT", 22, -6, 0 },
+ { "JTDOWNRG", 4, -20, 0 },
+ { "CTUPRIGH", 10, 0, 0 },
+ { "CTRIGHT", 26, -22, 0 },
+ { "CTDOWNRI", 16, 4, 0 },
+ { "ITUPRIGH", 0, 0, 0 },
+ { "ITRIGHT", 20, 0, 0 },
+ { "ITDOWNRG", 8, 0, 0 }
+};
+
+static const int WALK_SPEED_X[99] = {
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 90, 90, 90, 90, 90, 91, 90, 90,
+ 90, 90,100, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,100, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,103, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90
+};
+
+static const int WALK_SPEED_Y[99] = {
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 32, 32, 32, 28, 28, 28, 28, 28, 26, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 31, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
+};
+
+static const int WALK_SPEED_DIAG_X[99] = {
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 90, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50
+};
+
+/*----------------------------------------------------------------*/
+
+SavedNPCPath::SavedNPCPath() {
+ Common::fill(&_path[0], &_path[MAX_NPC_PATH], 0);
+ _npcIndex = 0;
+ _npcPause = 0;
+ _npcFacing = 0;
+ _lookHolmes = false;
+}
+
+SavedNPCPath::SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Common::Point &walkDest,
+ int npcFacing, bool lookHolmes) : _npcIndex(npcIndex), _npcPause(npcPause), _walkDest(walkDest),
+ _npcFacing(npcFacing), _lookHolmes(lookHolmes) {
+ Common::copy(&path[0], &path[MAX_NPC_PATH], &_path[0]);
+}
+
+/*----------------------------------------------------------------*/
+
+TattooPerson::TattooPerson() : Person() {
+ Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
+ _tempX = _tempScaleVal = 0;
+ _npcIndex = 0;
+ _npcMoved = false;
+ _npcFacing = -1;
+ _resetNPCPath = true;
+ _savedNpcSequence = 0;
+ _savedNpcFrame = 0;
+ _updateNPCPath = true;
+ _npcPause = 0;
+ _lookHolmes = false;
+}
+
+void TattooPerson::freeAltGraphics() {
+ if (_altImages != nullptr) {
+ delete _altImages;
+ _altImages = nullptr;
+ }
+
+ _altSeq = 0;
+}
+
+void TattooPerson::adjustSprite() {
+ People &people = *_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ if (_type == INVALID)
+ return;
+
+ if (_type == CHARACTER && _status) {
+ // Sprite waiting to move, so restart walk
+ _walkCount = _status;
+ _status = 0;
+
+ _walkDest = _walkTo.front();
+ setWalking();
+ } else if (_type == CHARACTER && _walkCount) {
+ if (_walkCount > 10) {
+ _walkDest = _nextDest;
+ setWalking();
+ }
+
+ _position += _delta;
+ if (_walkCount)
+ --_walkCount;
+
+ if (!_walkCount) {
+ // If there are remaining points to walk, move to the next one
+ if (!_walkTo.empty()) {
+ _walkDest = _walkTo.pop();
+ setWalking();
+ } else {
+ gotoStand();
+ }
+ }
+ }
+
+ if (_type != CHARACTER) {
+ if (_position.y > SHERLOCK_SCREEN_HEIGHT)
+ _position.y = SHERLOCK_SCREEN_HEIGHT;
+
+ if (_position.y < UPPER_LIMIT)
+ _position.y = UPPER_LIMIT;
+
+ if (_position.x < LEFT_LIMIT)
+ _position.x = LEFT_LIMIT;
+
+ if (_position.x > RIGHT_LIMIT)
+ _position.x = RIGHT_LIMIT;
+ }
+
+ int frameNum = _frameNumber;
+ if (frameNum == -1)
+ frameNum = 0;
+ int idx = _walkSequences[_sequenceNumber][frameNum];
+ if (idx > _maxFrames)
+ idx = 1;
+
+ // Set the image frame
+ if (_altSeq)
+ _imageFrame = &(*_altImages)[idx - 1];
+ else
+ _imageFrame = &(*_images)[idx - 1];
+
+ // See if the player has come to a stop after clicking on an Arrow zone to leave the scene.
+ // If so, this will set up the exit information for the scene transition
+ if (!_walkCount && ui._exitZone != -1 && scene._walkedInScene && scene._goToScene == -1 &&
+ !_description.compareToIgnoreCase(people[HOLMES]._description)) {
+ Exit &exit = scene._exits[ui._exitZone];
+ scene._goToScene = exit._scene;
+
+ if (exit._newPosition.x != 0) {
+ people._savedPos = exit._newPosition;
+
+ if (people._savedPos._facing > 100 && people._savedPos.x < 1)
+ people._savedPos.x = 100;
+ }
+ }
+}
+
+void TattooPerson::gotoStand() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+
+ // If the misc field is set, then we're running a special talk sequence, so don't interrupt it.
+ if (_misc)
+ return;
+
+ _walkTo.clear();
+ _walkCount = 0;
+ int oldFacing = _sequenceNumber;
+
+ // If the person was talking or listening, just return it to the standing sequence
+ // in the direction they were pointing
+ if (_sequenceNumber >= TALK_UPRIGHT && _sequenceNumber <= LISTEN_UPLEFT) {
+ switch (_sequenceNumber) {
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ _sequenceNumber = STOP_UPRIGHT;
+ break;
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ _sequenceNumber = STOP_RIGHT;
+ break;
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ _sequenceNumber = STOP_DOWNRIGHT;
+ break;
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ _sequenceNumber = STOP_DOWNLEFT;
+ break;
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ _sequenceNumber = STOP_LEFT;
+ break;
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ _sequenceNumber = STOP_UPLEFT;
+ break;
+ default:
+ break;
+ }
+
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ // Set the Frame number to the last frame so we don't move
+ _frameNumber = 0;
+
+ checkWalkGraphics();
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+ return;
+ }
+
+ // If the sprite that is stopping is an NPC and he is supposed to face a certain direction
+ // when he stops, set that direction here
+ int npc = -1;
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (_imageFrame == people[idx]._imageFrame)
+ npc = idx;
+ }
+
+ if (npc != -1 && people[npc]._npcFacing != -1) {
+ if (people[npc]._npcFacing == FACING_PLAYER) {
+ // See where Holmes is with respect to the NPC (x coords)
+ if (people[HOLMES]._position.x < people[npc]._position.x)
+ people[npc]._npcFacing = STOP_LEFT;
+ else
+ people[npc]._npcFacing = STOP_RIGHT;
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (people[HOLMES]._position.y < people[npc]._position.y - (10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is above the NPC so reset the facing to the diagonal ups
+ if (people[npc]._npcFacing == STOP_RIGHT)
+ people[npc]._npcFacing = STOP_UPRIGHT;
+ else
+ people[npc]._npcFacing = STOP_UPLEFT;
+ } else {
+ if (people[HOLMES]._position.y > people[npc]._position.y + (10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC so reset the facing to the diagonal downs
+ if (people[npc]._npcFacing == STOP_RIGHT)
+ people[npc]._npcFacing = STOP_DOWNRIGHT;
+ else
+ people[npc]._npcFacing = STOP_DOWNLEFT;
+ }
+ }
+ }
+
+ _sequenceNumber = people[npc]._npcFacing;
+ } else {
+ switch (_sequenceNumber) {
+ case WALK_UP: _sequenceNumber = STOP_UP; break;
+ case WALK_UPRIGHT: _sequenceNumber = STOP_UPRIGHT; break;
+ case WALK_RIGHT: _sequenceNumber = STOP_RIGHT; break;
+ case WALK_DOWNRIGHT: _sequenceNumber = STOP_DOWNRIGHT; break;
+ case WALK_DOWN: _sequenceNumber = STOP_DOWN; break;
+ case WALK_DOWNLEFT: _sequenceNumber = STOP_DOWNLEFT;break;
+ case WALK_LEFT: _sequenceNumber = STOP_LEFT; break;
+ case WALK_UPLEFT: _sequenceNumber = STOP_UPLEFT; break;
+ }
+ }
+
+ // Only restart the frame number at 0 if the new sequence is different from the last sequence
+ // so we don't let Holmes repeat standing.
+ if (_oldWalkSequence != -1) {
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ _frameNumber = 0;
+ }
+
+ checkWalkGraphics();
+
+ _oldWalkSequence = -1;
+ people._allowWalkAbort = true;
+}
+
+void TattooPerson::setWalking() {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ int oldDirection, oldFrame;
+ Common::Point delta;
+ _nextDest = _walkDest;
+
+ // Flag that player has now walked in the scene
+ scene._walkedInScene = true;
+
+ // Stop any previous walking, since a new dest is being set
+ _walkCount = 0;
+ oldDirection = _sequenceNumber;
+ oldFrame = _frameNumber;
+
+ // Set speed to use horizontal and vertical movement
+ int scaleVal = scene.getScaleVal(_position);
+ Common::Point speed(MAX(WALK_SPEED_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
+ MAX(WALK_SPEED_Y[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2));
+ Common::Point diagSpeed(MAX(WALK_SPEED_DIAG_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2),
+ MAX((WALK_SPEED_Y[scene._currentScene - 1] - 2) * SCALE_THRESHOLD / scaleVal, 2));
+
+ // If the player is already close to the given destination that no walking is needed,
+ // move to the next straight line segment in the overall walking route, if there is one
+ for (;;) {
+ if (_centerWalk || !_walkTo.empty()) {
+ // Since we want the player to be centered on the ultimate destination, and the player
+ // is drawn from the left side, move the cursor half the width of the player to center it
+ delta = Common::Point(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x,
+ _position.y / FIXED_INT_MULTIPLIER - _walkDest.y);
+
+ int dir;
+ if (ABS(delta.x) > ABS(delta.y))
+ dir = (delta.x < 0) ? WALK_LEFT : WALK_RIGHT;
+ else
+ dir = (delta.y < 0) ? WALK_UP : WALK_DOWN;
+
+ scaleVal = scene.getScaleVal(Point32(_walkDest.x * FIXED_INT_MULTIPLIER,
+ _walkDest.y * FIXED_INT_MULTIPLIER));
+ _walkDest.x -= _stopFrames[dir]->sDrawXSize(scaleVal) / 2;
+ }
+
+ delta = Common::Point(
+ ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x),
+ ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y)
+ );
+
+ // If we're ready to move a sufficient distance, that's it. Otherwise,
+ // move onto the next portion of the walk path, if there is one
+ if ((delta.x > 3 || delta.y > 0) || _walkTo.empty())
+ break;
+
+ // Pop next walk segment off the walk route stack
+ _walkDest = _walkTo.pop();
+ }
+
+ // If a sufficient move is being done, then start the move
+ if (delta.x > 3 || delta.y) {
+ // See whether the major movement is horizontal or vertical
+ if (delta.x >= delta.y) {
+ // Set the initial frame sequence for the left and right, as well
+ // as setting the delta x depending on direction
+ if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_LEFT;
+ _delta.x = speed.x * -(FIXED_INT_MULTIPLIER / 10);
+ } else {
+ _sequenceNumber = WALK_RIGHT;
+ _delta.x = speed.x * (FIXED_INT_MULTIPLIER / 10);
+ }
+
+ // See if the x delta is too small to be divided by the speed, since
+ // this would cause a divide by zero error
+ if ((delta.x * 10) >= speed.x) {
+ // Det the delta y
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / ((delta.x * 10) / speed.x);
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER))
+ _delta.y = -_delta.y;
+
+ // Set how many times we should add the delta to the player's position
+ _walkCount = (delta.x * 10) / speed.x;
+ } else {
+ // The delta x was less than the speed (ie. we're really close to
+ // the destination). So set delta to 0 so the player won't move
+ _delta = Point32(0, 0);
+ _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER);
+
+ _walkCount = 1;
+ }
+
+ // See if the sequence needs to be changed for diagonal walking
+ if (_delta.y > 1500) {
+ if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
+ _delta.x = _delta.x / speed.x * diagSpeed.x;
+ _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);
+
+ _walkCount = delta.x * 10 / diagSpeed.x;
+ }
+
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_DOWNLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_DOWNRIGHT;
+ break;
+ }
+ } else if (_delta.y < -1500) {
+ if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) {
+ _delta.x = _delta.x / speed.x * diagSpeed.x;
+ _delta.y = -1 * (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x);
+
+ _walkCount = (delta.x * 10) / diagSpeed.x;
+ }
+
+ switch (_sequenceNumber) {
+ case WALK_LEFT:
+ _sequenceNumber = WALK_UPLEFT;
+ break;
+ case WALK_RIGHT:
+ _sequenceNumber = WALK_UPRIGHT;
+ break;
+ }
+ }
+ } else {
+ // Major movement is vertical, so set the sequence for up and down,
+ // and set the delta Y depending on the direction
+ if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) {
+ _sequenceNumber = WALK_UP;
+ _delta.y = speed.y * -(FIXED_INT_MULTIPLIER / 10);
+ } else {
+ speed.y = diagSpeed.y;
+ _sequenceNumber = WALK_DOWN;
+ _delta.y = speed.y * (FIXED_INT_MULTIPLIER / 10);
+ }
+
+ // Set the delta x
+ if (delta.y * 10 / speed.y)
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y * 10 / speed.y);
+ else
+ _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / delta.y;
+
+ if (_walkDest.x < _position.y / FIXED_INT_MULTIPLIER)
+ _delta.x = -_delta.x;
+
+ // Set how many times we should add the delta's to the players position
+ if (delta.y * 10 / speed.y)
+ _walkCount = delta.y * 10 / speed.y;
+ else
+ _walkCount = delta.y;
+ }
+ }
+
+ // See if the new walk sequence is the same as the old. If it's a new one,
+ // we need to reset the frame number to zero so it's animation starts at
+ // it's beginning. Otherwise, if it's the same sequence, we can leave it
+ // as is, so it keeps the animation going at wherever it was up to
+ if (_sequenceNumber != _oldWalkSequence) {
+ if (_seqTo) {
+ // Reset to previous value
+ _walkSequences[oldDirection]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+ _frameNumber = 0;
+ }
+
+ checkWalkGraphics();
+ _oldWalkSequence = _sequenceNumber;
+
+ if (!_walkCount && _walkTo.empty())
+ gotoStand();
+
+ // If the sequence is the same as when we started, then Holmes was standing still and we're trying
+ // to re-stand him, so reset Holmes' rame to the old frame number from before it was reset to 0
+ if (_sequenceNumber == oldDirection)
+ _frameNumber = oldFrame;
+}
+
+void TattooPerson::walkToCoords(const Point32 &destPos, int destDir) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ CursorId oldCursor = events.getCursor();
+ events.setCursor(WAIT);
+
+ _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER, destPos.y / FIXED_INT_MULTIPLIER);
+
+ bool isHolmes = this == &people[HOLMES];
+ if (isHolmes) {
+ people._allowWalkAbort = true;
+ } else {
+ // Clear the path Variables
+ _npcIndex = _npcPause;
+ Common::fill(_npcPath, _npcPath + 100, 0);
+ _npcFacing = destDir;
+ }
+
+ _centerWalk = false;
+
+ // Only move the person if they're going an appreciable distance
+ if (ABS(_walkDest.x - (_position.x / FIXED_INT_MULTIPLIER)) > 8 ||
+ ABS(_walkDest.y - (_position.y / FIXED_INT_MULTIPLIER)) > 4) {
+ goAllTheWay();
+
+ do {
+ // Keep doing animations whilst walk is in progress
+ events.wait(1);
+ scene.doBgAnim();
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = 1;
+ talk._talkToAbort = true;
+ }
+ }
+ } while (!_vm->shouldQuit() && _walkCount && !talk._talkToAbort);
+ }
+
+ _centerWalk = true;
+ if (!isHolmes)
+ _updateNPCPath = true;
+
+ if (!talk._talkToAbort) {
+ // put character exactly on right spot
+ _position = destPos;
+
+ if (_sequenceNumber != destDir) {
+ // Facing character to correct ending direction
+ _sequenceNumber = destDir;
+ gotoStand();
+ }
+
+ if (!isHolmes)
+ _updateNPCPath = false;
+
+ // Secondary walking wait loop
+ bool done = false;
+ while (!done && !_vm->shouldQuit()) {
+ events.wait(1);
+ scene.doBgAnim();
+
+ // See if we're past the initial goto stand sequence
+ for (int idx = 0; idx < _frameNumber; ++idx) {
+ if (_walkSequences[_sequenceNumber][idx] == 0) {
+ done = true;
+ break;
+ }
+ }
+
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = 1;
+ talk._talkToAbort = true;
+ }
+ }
+ }
+
+ if (!isHolmes)
+ _updateNPCPath = true;
+
+ if (!talk._talkToAbort)
+ events.setCursor(oldCursor);
+ }
+}
+
+void TattooPerson::clearNPC() {
+ Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0);
+ _npcIndex = 0;
+ _pathStack.clear();
+ _npcName = "";
+}
+
+void TattooPerson::updateNPC() {
+ People &people = *_vm->_people;
+ Talk &talk = *_vm->_talk;
+
+ // If the NPC isn't on, or it's in Talk or Listen Mode, then return without doing anything
+ if (_type != CHARACTER || _sequenceNumber >= TALK_UPRIGHT)
+ return;
+
+ // If the NPC is paused, just decrement his pause counter and exit
+ if (_npcPause) {
+ // Decrement counter
+ --_npcPause;
+
+ // Now see if we need to update the NPC's frame sequence so that he faces Holmes
+ if (_lookHolmes) {
+ // See where Holmes is with respect to the NPC (x coordinate)
+ _npcFacing = (people[HOLMES]._position.x < _position.x) ? STOP_LEFT : STOP_RIGHT;
+
+ // See where Holmes is with respect to the NPC (y coordinate)
+ if (people[HOLMES]._position.y < (_position.y - 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is above the NPC so reset the facing to a diagonal up
+ _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
+ } else if (people[HOLMES]._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC so reset the facing to a diagonal down
+ _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
+ }
+
+ // See if we need to set the old_walk_sequence so the NPC will put his arms
+ // up if he turns another way
+ if (_sequenceNumber != _npcFacing)
+ _oldWalkSequence = _sequenceNumber;
+
+ gotoStand();
+ }
+ } else {
+ // Reset the look flag so the NPC won't face Holmes anymore
+ _lookHolmes = false;
+
+ // See if the NPC is stopped or not. Don't do anything if he's moving
+ if (!_walkCount) {
+ // If there is no new command, reset the path back to the beginning
+ if (!_npcPath[_npcIndex])
+ _npcIndex = 0;
+
+ // The NPC is stopped and any pause he was doing is done. We can now see what
+ // the next command in the NPC path is.
+
+ // Scan Past any NPC Path Labels since they do nothing except mark places for If's and Goto's
+ while (_npcPath[_npcIndex] == NPCPATH_PATH_LABEL)
+ _npcIndex += 2;
+
+ if (_npcPath[_npcIndex]) {
+ _npcFacing = -1;
+
+ switch (_npcPath[_npcIndex]) {
+ case NPCPATH_SET_DEST: {
+ // Set the NPC's new destination
+ int xp = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ _walkDest.x = xp;
+ _walkDest.y = (_npcPath[_npcIndex + 3] - 1) * 256 + _npcPath[_npcIndex + 4] - 1;
+ _npcFacing = _npcPath[_npcIndex + 5] - 1;
+
+ goAllTheWay();
+ _npcIndex += 6;
+ break;
+ }
+
+ case NPCPATH_PAUSE:
+ // Set the NPC to pause where he is
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _npcIndex += 3;
+ break;
+
+ case NPCPATH_SET_TALK_FILE: {
+ // Set the NPC's Talk File to use if Holmes talks to them
+ ++_npcIndex;
+
+ _npcName = "";
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_npcPath[_npcIndex + idx] != '~')
+ _npcName += _npcPath[_npcIndex + idx];
+ else
+ break;
+ }
+
+ _npcIndex += 8;
+ break;
+ }
+
+ case NPCPATH_CALL_TALK_FILE: {
+ // Call a Talk File
+ ++_npcIndex;
+
+ Common::String name;
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_npcPath[_npcIndex + idx] != '~')
+ name += _npcPath[_npcIndex + idx];
+ else
+ break;
+ }
+
+ _npcIndex += 8;
+ talk.talkTo(name);
+ break;
+ }
+
+ case NPCPATH_TAKE_NOTES:
+ // Set the NPC to Pause where he is and set his frame sequences
+ // so he takes notes while he's paused
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _npcIndex += 3;
+ break;
+
+ case NPCPATH_FACE_HOLMES:
+ // Set the NPC to Pause where he is and set his look flag so he will always face Holmes
+ // while he is paused
+ _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1;
+ _lookHolmes = true;
+ _npcIndex += 3;
+ break;
+
+ //case NPCPATH_PATH_LABEL: // No implementation needed here
+
+ case NPCPATH_GOTO_LABEL: {
+ // Goto NPC Path Label
+ int label = _npcPath[_npcIndex + 1];
+ _npcIndex = 0;
+
+ // Scan through NPC path data to find the label
+ bool found = false;
+ while (!found) {
+ switch (_npcPath[_npcIndex]) {
+ case NPCPATH_SET_DEST:
+ _npcIndex += 6;
+ break;
+ case NPCPATH_PAUSE:
+ case NPCPATH_TAKE_NOTES:
+ case NPCPATH_FACE_HOLMES:
+ _npcIndex += 3;
+ break;
+ case NPCPATH_SET_TALK_FILE:
+ case NPCPATH_CALL_TALK_FILE:
+ _npcIndex += 8;
+ break;
+ case NPCPATH_PATH_LABEL:
+ if (_npcPath[_npcIndex + 1] == label)
+ found = true;
+ _npcIndex += 2;
+ break;
+ case NPCPATH_GOTO_LABEL:
+ _npcIndex += 2;
+ break;
+ case NPCPATH_IFFLAG_GOTO_LABEL:
+ _npcIndex += 4;
+ break;
+ }
+ }
+ break;
+ }
+
+ case NPCPATH_IFFLAG_GOTO_LABEL: {
+ // If FLAG then Goto Label
+ int flag = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1 - (_npcPath[_npcIndex + 2] == 1 ? 1 : 0);
+
+ // Set the value the flag should be for the if statement to succeed
+ bool flagVal = flag < 16384;
+
+ int label = _npcPath[_npcIndex + 3];
+ _npcIndex += 4;
+
+ // If the flag is set Correctly, move the NPC Index to the given label
+ if (_vm->readFlags(flag & 16383) == flagVal) {
+ _npcIndex = 0;
+ bool found = false;
+ while (!found)
+ {
+ switch (_npcPath[_npcIndex])
+ {
+ case NPCPATH_SET_DEST:
+ _npcIndex += 6;
+ break;
+ case NPCPATH_PAUSE:
+ case NPCPATH_TAKE_NOTES:
+ case NPCPATH_FACE_HOLMES:
+ _npcIndex += 3;
+ break;
+ case NPCPATH_SET_TALK_FILE:
+ case NPCPATH_CALL_TALK_FILE:
+ _npcIndex += 8;
+ break;
+ case NPCPATH_PATH_LABEL:
+ if (_npcPath[_npcIndex + 1] == label)
+ found = true;
+ _npcIndex += 2;
+ break;
+ case NPCPATH_GOTO_LABEL:
+ _npcIndex += 2;
+ break;
+ case NPCPATH_IFFLAG_GOTO_LABEL:
+ _npcIndex += 4;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+void TattooPerson::pushNPCPath() {
+ assert(_pathStack.size() < 2);
+ SavedNPCPath savedPath(_npcPath, _npcIndex, _npcPause, _position, _sequenceNumber, _lookHolmes);
+ _pathStack.push(savedPath);
+}
+
+void TattooPerson::pullNPCPath() {
+ // Pop the stack entry and restore the fields
+ SavedNPCPath path = _pathStack.pop();
+ Common::copy(&path._path[0], &path._path[MAX_NPC_PATH], &_npcPath[0]);
+ _npcIndex = path._npcIndex;
+ _npcPause = path._npcPause;
+
+ // Handle the first case if the NPC was paused
+ if (_npcPause) {
+ _walkDest = Common::Point(path._walkDest.x / FIXED_INT_MULTIPLIER, path._walkDest.y / FIXED_INT_MULTIPLIER);
+ _npcFacing = path._npcFacing;
+ _lookHolmes = path._lookHolmes;
+
+ // See if the NPC was moved
+ if (_walkDest.x != (_position.x / FIXED_INT_MULTIPLIER) ||
+ _walkDest.y != (_position.y / FIXED_INT_MULTIPLIER)) {
+ goAllTheWay();
+ _npcPause = 0;
+ _npcIndex -= 3;
+ } else {
+ // See if we need to set the old walk sequence so the NPC will put his arms up if he turns another way
+ if (_npcFacing != _sequenceNumber)
+ _oldWalkSequence = _sequenceNumber;
+
+ gotoStand();
+ }
+ } else {
+ // Handle the second case if the NPC was in motion
+ _npcIndex -= 6;
+ }
+}
+
+Common::Point TattooPerson::getSourcePoint() const {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ int scaleVal = scene.getScaleVal(_position);
+
+ return Common::Point(_position.x / FIXED_INT_MULTIPLIER + _imageFrame->sDrawXSize(scaleVal) / 2,
+ _position.y / FIXED_INT_MULTIPLIER);
+}
+
+void TattooPerson::setObjTalkSequence(int seq) {
+ assert(seq != -1 && _type == CHARACTER);
+
+ if (_seqTo) {
+ // reset to previous value
+ _walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo;
+ _seqTo = 0;
+ }
+
+ _sequenceNumber = _gotoSeq;
+ _frameNumber = 0;
+ checkWalkGraphics();
+}
+
+void TattooPerson::checkWalkGraphics() {
+ People &people = *_vm->_people;
+
+ if (_images == nullptr) {
+ freeAltGraphics();
+ return;
+ }
+
+ Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str());
+
+ // Set the adjust depending on if we have to fine tune the x position of this particular graphic
+ _adjust.x = _adjust.y = 0;
+
+ for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) {
+ if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName,
+ strlen(ADJUST_WALKS[idx]._vgsName))) {
+ if (_walkSequences[_sequenceNumber]._horizFlip)
+ _adjust.x = ADJUST_WALKS[idx]._flipXAdjust;
+ else
+ _adjust.x = ADJUST_WALKS[idx]._xAdjust;
+
+ _adjust.y = ADJUST_WALKS[idx]._yAdjust;
+ break;
+ }
+ }
+
+ // See if we're already using Alternate Graphics
+ if (_altSeq) {
+ // See if the VGS file called for is different than the alternate graphics already loaded
+ if (!_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) {
+ // Different AltGraphics, Free the old ones
+ freeAltGraphics();
+ }
+ }
+
+ // If there is no Alternate Sequence set, see if we need to load a new one
+ if (!_altSeq) {
+ int npcNum = -1;
+ // Find which NPC this is so we can check the name of the graphics loaded
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (this == &people[idx]) {
+ npcNum = idx;
+ break;
+ }
+ }
+
+ if (npcNum != -1) {
+ // See if the VGS file called for is different than the main graphics which are already loaded
+ if (filename.compareToIgnoreCase(people[npcNum]._walkVGSName) != 0) {
+ // See if this is one of the more used Walk Graphics stored in WALK.LIB
+ for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) {
+ if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) {
+ people._useWalkLib = true;
+ break;
+ }
+ }
+
+ _altImages = new ImageFile(filename);
+ people._useWalkLib = false;
+
+ _altSeq = _sequenceNumber + 1;
+ }
+ }
+ }
+
+ // If this is a different seqeunce from the current sequence, reset the appropriate variables
+ if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) {
+ _seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0;
+ _sequences = &_walkSequences[_sequenceNumber]._sequences[0];
+ _seqSize = _walkSequences[_sequenceNumber]._sequences.size();
+ }
+
+ setImageFrame();
+}
+
+void TattooPerson::synchronize(Serializer &s) {
+ s.syncAsSint32LE(_position.x);
+ s.syncAsSint32LE(_position.y);
+ s.syncAsSint16LE(_sequenceNumber);
+ s.syncAsSint16LE(_type);
+ s.syncString(_walkVGSName);
+ s.syncString(_description);
+ s.syncString(_examine);
+
+ // NPC specific properties
+ s.syncBytes(&_npcPath[0], MAX_NPC_PATH);
+ s.syncString(_npcName);
+ s.syncAsSint32LE(_npcPause);
+ s.syncAsByte(_lookHolmes);
+ s.syncAsByte(_updateNPCPath);
+
+ // Walk to list
+ uint count = _walkTo.size();
+ s.syncAsUint16LE(count);
+ if (s.isLoading()) {
+ // Load path
+ for (uint idx = 0; idx < count; ++idx) {
+ int xp = 0, yp = 0;
+ s.syncAsSint16LE(xp);
+ s.syncAsSint16LE(yp);
+ _walkTo.push(Common::Point(xp, yp));
+ }
+ } else {
+ // Save path
+ Common::Array<Common::Point> path;
+
+ // Save the points of the path
+ for (uint idx = 0; idx < count; ++idx) {
+ Common::Point pt = _walkTo.pop();
+ s.syncAsSint16LE(pt.x);
+ s.syncAsSint16LE(pt.y);
+ path.push_back(pt);
+ }
+
+ // Re-add the pending points back to the _walkTo queue
+ for (uint idx = 0; idx < count; ++idx)
+ _walkTo.push(path[idx]);
+ }
+
+ // Verbs
+ for (int idx = 0; idx < 2; ++idx)
+ _use[idx].synchronize(s);
+}
+
+void TattooPerson::walkHolmesToNPC() {
+ Events &events = *_vm->_events;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ TattooPerson &holmes = people[HOLMES];
+ int facing;
+
+ // If the NPC is moving, stop him at his current position
+ if (_walkCount) {
+ // Reset the facing so the NPC will stop facing the direction he was going,
+ // rather than the direction he was supposed to when he finished wlaking
+ _npcFacing = -1;
+ gotoStand();
+ }
+
+ int scaleVal = scene.getScaleVal(_position);
+ ImageFrame &imgFrame = (*holmes._images)[0];
+
+ // Clear the path variables
+ memset(_npcPath, 0, 100);
+
+ // Set the NPC path so he pauses for 250 while looking at Holmes
+ _npcPath[0] = 6;
+ _npcPath[1] = 1;
+ _npcPath[2] = 251;
+ _npcIndex = 0;
+ _npcPause = 250;
+ _lookHolmes = true;
+
+ // See where Holmes is with respect to the NPC (x coords)
+ if (holmes._position.x < _position.x) {
+ holmes._walkDest.x = MAX(_position.x / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal), 0);
+ } else {
+ holmes._walkDest.x = MIN(_position.x / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) * 2,
+ screen._backBuffer1.w() - 1);
+ }
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (holmes._position.y < (_position.y - imgFrame.sDrawXSize(scaleVal) * 500)) {
+ holmes._walkDest.y = MAX(_position.y / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal) / 2, 0);
+ } else {
+ if (holmes._position.y > (_position.y + imgFrame.sDrawXSize(scaleVal) * 500)) {
+ // Holmes is below the NPC
+ holmes._walkDest.y = MIN(_position.y / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) / 2,
+ SHERLOCK_SCREEN_HEIGHT - 1);
+ } else {
+ // Holmes is roughly on the same Y as the NPC
+ holmes._walkDest.y = _position.y / FIXED_INT_MULTIPLIER;
+ }
+ }
+
+ events.setCursor(WAIT);
+
+ _walkDest.x += 10;
+ people._allowWalkAbort = true;
+ holmes.goAllTheWay();
+
+ // Do doBgAnim should be called over and over until walk is done
+ do {
+ events.wait(1);
+ scene.doBgAnim();
+ } while (holmes._walkCount);
+
+ if (!talk._talkToAbort) {
+ // Setup correct direction for Holmes to face
+
+ // See where Holmes is with respect to the NPC (x coords)
+ facing = (holmes._position.x < _position.x) ? STOP_RIGHT : STOP_LEFT;
+
+ // See where Holmes is with respect to the NPC (y coords)
+ if (holmes._position.y < (_position.y - (10 * FIXED_INT_MULTIPLIER))) {
+ // Holmes is above the NPC. Reset the facing to the diagonal downs
+ facing = (facing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT;
+ } else {
+ if (holmes._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) {
+ // Holmes is below the NPC. Reset the facing to the diagonal ups
+ facing = (facing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT;
+ }
+ }
+
+ holmes._sequenceNumber = facing;
+ holmes.gotoStand();
+
+ events.setCursor(ARROW);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+TattooPeople::TattooPeople(SherlockEngine *vm) : People(vm) {
+ for (int idx = 0; idx < 6; ++idx)
+ _data.push_back(new TattooPerson());
+}
+
+void TattooPeople::setListenSequence(int speaker, int sequenceNum) {
+ Scene &scene = *_vm->_scene;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ int objNum = findSpeaker(speaker);
+ if (objNum < 256 && objNum != -1) {
+ // See if the Object has to wait for an Abort Talk Code
+ Object &obj = scene._bgShapes[objNum];
+ if (obj.hasAborts())
+ obj._gotoSeq = sequenceNum;
+ else
+ obj.setObjTalkSequence(sequenceNum);
+ } else if (objNum != -1) {
+ objNum -= 256;
+ TattooPerson &person = (*this)[objNum];
+
+ int newDir = person._sequenceNumber;
+ switch (person._sequenceNumber) {
+ case WALK_UP:
+ case STOP_UP:
+ case WALK_UPRIGHT:
+ case STOP_UPRIGHT:
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ newDir = LISTEN_UPRIGHT;
+ break;
+ case WALK_RIGHT:
+ case STOP_RIGHT:
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ newDir = LISTEN_RIGHT;
+ break;
+ case WALK_DOWNRIGHT:
+ case STOP_DOWNRIGHT:
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ newDir = LISTEN_DOWNRIGHT;
+ break;
+ case WALK_DOWN:
+ case STOP_DOWN:
+ case WALK_DOWNLEFT:
+ case STOP_DOWNLEFT:
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ newDir = LISTEN_DOWNLEFT;
+ break;
+ case WALK_LEFT:
+ case STOP_LEFT:
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ newDir = LISTEN_LEFT;
+ break;
+ case WALK_UPLEFT:
+ case STOP_UPLEFT:
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ newDir = LISTEN_UPLEFT;
+ break;
+
+ default:
+ break;
+ }
+
+ // See if the NPC's Seq has to wait for an Abort Talk Code
+ if (person.hasAborts()) {
+ person._gotoSeq = newDir;
+ } else {
+ if (person._seqTo) {
+ // Reset to previous value
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ person._sequenceNumber = newDir;
+ person._frameNumber = 0;
+ person.checkWalkGraphics();
+ }
+ }
+}
+
+void TattooPeople::setTalkSequence(int speaker, int sequenceNum) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+
+ // If no speaker is specified, then nothing needs to be done
+ if (speaker == -1)
+ return;
+
+ int objNum = people.findSpeaker(speaker);
+ if (objNum != -1 && objNum < 256) {
+ Object &obj = scene._bgShapes[objNum];
+
+ // See if the Object has to wait for an Abort Talk Code
+ if (obj.hasAborts()) {
+ talk.pushTalkSequence(&obj);
+ obj._gotoSeq = sequenceNum;
+ }
+ else {
+ obj.setObjTalkSequence(sequenceNum);
+ }
+ }
+ else if (objNum != -1) {
+ objNum -= 256;
+ TattooPerson &person = people[objNum];
+ int newDir = person._sequenceNumber;
+
+ switch (newDir) {
+ case WALK_UP:
+ case STOP_UP:
+ case WALK_UPRIGHT:
+ case STOP_UPRIGHT:
+ case TALK_UPRIGHT:
+ case LISTEN_UPRIGHT:
+ newDir = TALK_UPRIGHT;
+ break;
+ case WALK_RIGHT:
+ case STOP_RIGHT:
+ case TALK_RIGHT:
+ case LISTEN_RIGHT:
+ newDir = TALK_RIGHT;
+ break;
+ case WALK_DOWNRIGHT:
+ case STOP_DOWNRIGHT:
+ case TALK_DOWNRIGHT:
+ case LISTEN_DOWNRIGHT:
+ newDir = TALK_DOWNRIGHT;
+ break;
+ case WALK_DOWN:
+ case STOP_DOWN:
+ case WALK_DOWNLEFT:
+ case STOP_DOWNLEFT:
+ case TALK_DOWNLEFT:
+ case LISTEN_DOWNLEFT:
+ newDir = TALK_DOWNLEFT;
+ break;
+ case WALK_LEFT:
+ case STOP_LEFT:
+ case TALK_LEFT:
+ case LISTEN_LEFT:
+ newDir = TALK_LEFT;
+ break;
+ case WALK_UPLEFT:
+ case STOP_UPLEFT:
+ case TALK_UPLEFT:
+ case LISTEN_UPLEFT:
+ newDir = TALK_UPLEFT;
+ break;
+ default:
+ break;
+ }
+
+ // See if the NPC's sequence has to wait for an Abort Talk Code
+ if (person.hasAborts()) {
+ person._gotoSeq = newDir;
+ } else {
+ if (person._seqTo) {
+ // Reset to previous value
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ person._sequenceNumber = newDir;
+ person._frameNumber = 0;
+ person.checkWalkGraphics();
+ }
+ }
+}
+
+
+int TattooPeople::findSpeaker(int speaker) {
+ int result = People::findSpeaker(speaker);
+ const char *portrait = _characters[speaker]._portrait;
+
+ // Fallback that Rose Tattoo uses if no speaker was found
+ if (result == -1) {
+ bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES);
+
+ if (_data[HOLMES]->_type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag)))
+ return -1;
+
+ for (uint idx = 1; idx < _data.size(); ++idx) {
+ TattooPerson &p = (*this)[idx];
+
+ if (p._type == CHARACTER) {
+ Common::String name(p._name.c_str(), p._name.c_str() + 4);
+
+ if (name.equalsIgnoreCase(portrait) && p._npcName[4] >= '0' && p._npcName[4] <= '9')
+ return idx + 256;
+ }
+ }
+ }
+
+ return -1;
+}
+
+void TattooPeople::synchronize(Serializer &s) {
+ s.syncAsByte(_holmesOn);
+
+ for (uint idx = 0; idx < _data.size(); ++idx)
+ (*this)[idx].synchronize(s);
+
+ s.syncAsSint16LE(_holmesQuotient);
+
+ if (s.isLoading()) {
+ _savedPos.x = _data[HOLMES]->_position.x;
+ _savedPos.y = _data[HOLMES]->_position.y;
+ _savedPos._facing = _data[HOLMES]->_sequenceNumber;
+ }
+}
+
+bool TattooPeople::loadWalk() {
+ Resources &res = *_vm->_res;
+ bool result = false;
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ Person &person = *_data[idx];
+
+ if (!person._walkLoaded && (person._type == CHARACTER || person._type == HIDDEN_CHARACTER)) {
+ if (person._type == HIDDEN_CHARACTER)
+ person._type = INVALID;
+
+ // See if this is one of the more used Walk Graphics stored in WALK.LIB
+ for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) {
+ if (!person._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) {
+ _useWalkLib = true;
+ break;
+ }
+ }
+
+ // Load the images for the character
+ person._images = new ImageFile(person._walkVGSName, false);
+ person._maxFrames = person._images->size();
+
+ // Load walk sequence data
+ Common::String fname = Common::String(person._walkVGSName.c_str(), strchr(person._walkVGSName.c_str(), '.'));
+ fname += ".SEQ";
+
+ // Load the walk sequence data
+ Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib");
+
+ person._walkSequences.resize(stream->readByte());
+
+ for (uint seqNum = 0; seqNum < person._walkSequences.size(); ++seqNum)
+ person._walkSequences[seqNum].load(*stream);
+
+ // Close the sequences resource
+ delete stream;
+ _useWalkLib = false;
+
+ person._sequences = &person._walkSequences[person._sequenceNumber]._sequences[0];
+ person._seqSize = person._walkSequences[person._sequenceNumber]._sequences.size();
+ person._frameNumber = 0;
+ person.setImageFrame();
+
+ // Set the stop Frames pointers
+ for (int dirNum = 0; dirNum < 8; ++dirNum) {
+ int count = 0;
+ while (person._walkSequences[dirNum + 8][count] != 0)
+ ++count;
+ count += 2;
+ count = person._walkSequences[dirNum + 8][count] - 1;
+ person._stopFrames[dirNum] = &(*person._images)[count];
+ }
+
+ result = true;
+ person._walkLoaded = true;
+ } else if (person._type != CHARACTER) {
+ person._walkLoaded = false;
+ }
+ }
+
+ _forceWalkReload = false;
+ return result;
+}
+
+
+void TattooPeople::pullNPCPaths() {
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ TattooPerson &p = (*this)[idx];
+ if (p._npcMoved) {
+ while (!p._pathStack.empty())
+ p.pullNPCPath();
+ }
+ }
+}
+
+const Common::Point TattooPeople::restrictToZone(int zoneId, const Common::Point &destPos) {
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Common::Rect &r = scene._zones[zoneId];
+
+ if (destPos.x < 0 || destPos.x > screen._backBuffer1.w())
+ return destPos;
+ else if (destPos.y < r.top && r.left < destPos.x && destPos.x < r.right)
+ return Common::Point(destPos.x, r.top);
+ else if (destPos.y > r.bottom && r.left < destPos.x && destPos.x < r.right)
+ return Common::Point(destPos.x, r.bottom);
+ else if (destPos.x < r.left && r.top < destPos.y && destPos.y < r.bottom)
+ return Common::Point(r.left, destPos.y);
+ else if (destPos.x > r.right && r.top < destPos.y && destPos.y < r.bottom)
+ return Common::Point(r.bottom, destPos.y);
+
+ // Find which corner of the zone the point is closet to
+ if (destPos.x <= r.left) {
+ return Common::Point(r.left, (destPos.y <= r.top) ? r.top : r.bottom);
+ } else {
+ return Common::Point(r.right, (destPos.y <= r.top) ? r.top : r.bottom);
+ }
+}
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_people.h b/engines/sherlock/tattoo/tattoo_people.h
new file mode 100644
index 0000000000..fb3f6e7628
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_people.h
@@ -0,0 +1,270 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_PEOPLE_H
+#define SHERLOCK_TATTOO_PEOPLE_H
+
+#include "common/scummsys.h"
+#include "common/stack.h"
+#include "sherlock/people.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+// Animation sequence identifiers for characters
+enum TattooSequences {
+ // Walk Sequences Numbers for NPCs
+ WALK_UP = 0,
+ WALK_UPRIGHT = 1,
+ WALK_RIGHT = 2,
+ WALK_DOWNRIGHT = 3,
+ WALK_DOWN = 4,
+ WALK_DOWNLEFT = 5,
+ WALK_LEFT = 6,
+ WALK_UPLEFT = 7,
+
+ // Stop Sequences Numbers for NPCs
+ STOP_UP = 8,
+ STOP_UPRIGHT = 9,
+ STOP_RIGHT = 10,
+ STOP_DOWNRIGHT = 11,
+ STOP_DOWN = 12,
+ STOP_DOWNLEFT = 13,
+ STOP_LEFT = 14,
+ STOP_UPLEFT = 15,
+
+ // NPC Talk Sequence Numbers
+ TALK_UPRIGHT = 16,
+ TALK_RIGHT = 17,
+ TALK_DOWNRIGHT = 18,
+ TALK_DOWNLEFT = 19,
+ TALK_LEFT = 20,
+ TALK_UPLEFT = 21,
+
+ // NPC Listen Sequence Numbers
+ LISTEN_UPRIGHT = 22,
+ LISTEN_RIGHT = 23,
+ LISTEN_DOWNRIGHT = 24,
+ LISTEN_DOWNLEFT = 25,
+ LISTEN_LEFT = 26,
+ LISTEN_UPLEFT = 27
+};
+
+enum NpcPath {
+ NPCPATH_SET_DEST = 1,
+ NPCPATH_PAUSE = 2,
+ NPCPATH_SET_TALK_FILE = 3,
+ NPCPATH_CALL_TALK_FILE = 4,
+ NPCPATH_TAKE_NOTES = 5,
+ NPCPATH_FACE_HOLMES = 6,
+ NPCPATH_PATH_LABEL = 7,
+ NPCPATH_GOTO_LABEL = 8,
+ NPCPATH_IFFLAG_GOTO_LABEL = 9
+};
+
+struct SavedNPCPath {
+ byte _path[MAX_NPC_PATH];
+ int _npcIndex;
+ int _npcPause;
+ Common::Point _walkDest;
+ int _npcFacing;
+ bool _lookHolmes;
+
+ SavedNPCPath();
+ SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Common::Point &walkDest,
+ int npcFacing, bool lookHolmes);
+};
+
+class TattooPerson: public Person {
+private:
+ Point32 _nextDest;
+private:
+ bool checkCollision() const;
+
+ /**
+ * Free the alternate graphics used by NPCs
+ */
+ void freeAltGraphics();
+protected:
+ /**
+ * Get the source position for a character potentially affected by scaling
+ */
+ virtual Common::Point getSourcePoint() const;
+public:
+ Common::Stack<SavedNPCPath> _pathStack;
+ int _npcIndex;
+ int _npcPause;
+ byte _npcPath[MAX_NPC_PATH];
+ bool _npcMoved;
+ int _npcFacing;
+ bool _resetNPCPath;
+ int _savedNpcSequence;
+ int _savedNpcFrame;
+ int _tempX;
+ int _tempScaleVal;
+ bool _updateNPCPath;
+ bool _lookHolmes;
+public:
+ TattooPerson();
+ virtual ~TattooPerson() {}
+
+ /**
+ * Clear the NPC related data
+ */
+ void clearNPC();
+
+ /**
+ * Called from doBgAnim to move NPCs along any set paths. If an NPC is paused in his path,
+ * he will remain paused until his pause timer runs out. If he is walking somewhere,
+ * he will continue walking there until he reaches the dest position. When an NPC stops moving,
+ * the next element of his path is processed.
+ *
+ * The path is an array of bytes with control codes followed by their parameters as needed.
+ */
+ void updateNPC();
+
+ /**
+ * Push the NPC's path data onto the path stack for when a talk file moves the NPC that
+ * has some control codes.
+ */
+ void pushNPCPath();
+
+ /**
+ * Pull an NPC's path data that has been previously saved on the path stack for that character.
+ * There are two possibilities for when the NPC was interrupted, and both are handled differently:
+ * 1) The NPC was paused at a position
+ * If the NPC didn't move, we can just restore his pause counter and exit. But if he did move,
+ * he must return to that position, and the path index must be reset to the pause he was executing.
+ * This means that the index must be decremented by 3
+ * 2) The NPC was in route to a position
+ * He must be set to walk to that position again. This is done by moving the path index
+ * so that it points to the code that set the NPC walking there in the first place.
+ * The regular calls to updateNPC will handle the rest
+ */
+ void pullNPCPath();
+
+ /**
+ * Checks a sprite associated with an NPC to see if the frame sequence specified
+ * in the sequence number uses alternate graphics, and if so if they need to be loaded
+ */
+ void checkWalkGraphics();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ void synchronize(Serializer &s);
+
+ /**
+ * This adjusts the sprites position, as well as it's animation sequence:
+ */
+ virtual void adjustSprite();
+
+ /**
+ * Bring a moving character to a standing position
+ */
+ virtual void gotoStand();
+
+ /**
+ * Set the variables for moving a character from one poisition to another
+ * in a straight line
+ */
+ virtual void setWalking();
+
+ /**
+ * Walk to the co-ordinates passed, and then face the given direction
+ */
+ virtual void walkToCoords(const Point32 &destPos, int destDir);
+
+ /**
+ * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker
+ * so that it points to the beginning of the sequence number's talk sequence in the object's
+ * sequence buffer
+ * @param seq Which sequence to use (if there's more than 1)
+ * @remarks 1: First talk seq, 2: second talk seq, etc.
+ */
+ virtual void setObjTalkSequence(int seq);
+
+ /**
+ * Walk Holmes to the NPC
+ */
+ void walkHolmesToNPC();
+};
+
+class TattooPeople : public People {
+public:
+ TattooPeople(SherlockEngine *vm);
+ virtual ~TattooPeople() {}
+
+ TattooPerson &operator[](PeopleId id) { return *(TattooPerson *)_data[id]; }
+ TattooPerson &operator[](int idx) { return *(TattooPerson *)_data[idx]; }
+
+ /**
+ * If the specified speaker is a background object, it will set it so that it uses
+ * the Listen Sequence (specified by the sequence number). If the current sequence
+ * has an Allow Talk Code in it, the _gotoSeq field will be set so that the object
+ * begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code,
+ * the Listen Sequence will begin immediately.
+ * @param speaker Who is speaking
+ * @param sequenceNum Which listen sequence to use
+ */
+ void setListenSequence(int speaker, int sequenceNum = 1);
+
+ /**
+ * Restore any saved NPC walk path data from any of the NPCs
+ */
+ void pullNPCPaths();
+
+ /**
+ * Finds the scene background object corresponding to a specified speaker
+ */
+ virtual int findSpeaker(int speaker);
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Change the sequence of the scene background object associated with the specified speaker.
+ */
+ virtual void setTalkSequence(int speaker, int sequenceNum = 1);
+
+ /**
+ * Load the walking images for Sherlock
+ */
+ virtual bool loadWalk();
+
+ /**
+ * Restrict passed point to zone using Sherlock's positioning rules
+ */
+ virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos);
+};
+
+} // End of namespace Scalpel
+
+} // End of namespace Sherlock
+
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_resources.cpp b/engines/sherlock/tattoo/tattoo_resources.cpp
new file mode 100644
index 0000000000..3be41e2650
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_resources.cpp
@@ -0,0 +1,329 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_resources.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+const char PORTRAITS[TATTOO_MAX_PEOPLE][5] = {
+ { "HOLM" }, // Sherlock Holmes
+ { "WATS" }, // Dr. Watson
+ { "HUDS" }, // Mrs. Hudson
+ { "FORB" }, // Stanley Forbes
+ { "MYCR" }, // Mycroft Holmes
+ { "WIGG" }, // Wiggins
+ { "BURN" }, // Police Constable Burns
+ { "TRIM" }, // Augustus Trimble
+ { "DALE" }, // Police Constable Daley
+ { "MATR" }, // Matron
+ { "GRAC" }, // Sister Grace
+ { "MCCA" }, // Preston McCabe
+ { "COLL" }, // Bob Colleran
+ { "JONA" }, // Jonas Rigby
+ { "ROAC" }, // Police Constable Roach
+ { "DEWA" }, // James Dewar
+ { "JERE" }, // Sergeant Jeremy Duncan
+ { "GREG" }, // Inspector Gregson
+ { "LEST" }, // Inspector Lestrade
+ { "NEED" }, // Jesse Needhem
+ { "FLEM" }, // Arthur Fleming
+ { "PRAT" }, // Mr. Thomas Pratt
+ { "TILL" }, // Mathilda (Tillie) Mason
+ { "RUSS" }, // Adrian Russell
+ { "WHIT" }, // Eldridge Whitney
+ { "HEPP" }, // Hepplethwaite
+ { "HORA" }, // Horace Silverbridge
+ { "SHER" }, // Old Sherman
+ { "VERN" }, // Maxwell Verner
+ { "REDD" }, // Millicent Redding
+ { "VIRG" }, // Virgil Silverbridge
+ { "GEOR" }, // George O'Keeffe
+ { "LAWT" }, // Lord Denys Lawton
+ { "JENK" }, // Jenkins
+ { "JOCK" }, // Jock Mahoney
+ { "BART" }, // Bartender
+ { "LADY" }, // Lady Cordelia Lockridge
+ { "PETT" }, // Pettigrew
+ { "FANS" }, // Sir Avery Fanshawe
+ { "HODG" }, // Hodgkins
+ { "WILB" }, // Wilbur "Birdy" Heywood
+ { "JACO" }, // Jacob Farthington
+ { "BLED" }, // Philip Bledsoe
+ { "FOWL" }, // Sidney Fowler
+ { "PROF" }, // Professor Theodore Totman
+ { "ROSE" }, // Rose Hinchem
+ { "TALL" }, // Tallboy
+ { "STIT" }, // Ethlebert "Stitch" Rumsey
+ { "FREE" }, // Charles Freedman
+ { "HEMM" }, // Nigel Hemmings
+ { "CART" }, // Fairfax Carter
+ { "WILH" }, // Wilhelm II
+ { "WACH" }, // Wachthund
+ { "WILS" }, // Jonathan Wilson
+ { "DAVE" }, // David Lloyd-Jones
+ { "HARG" }, // Edward Hargrove
+ { "MORI" }, // Professor James Moriarty
+ { "LASC" }, // The Lascar
+ { "PARR" }, // Parrot
+ { "SCAR" }, // Vincent Scarrett
+ { "ALEX" }, // Alexandra
+ { "QUEE" }, // Queen Victoria
+ { "JOHN" }, // John Brown
+ { "PAT1" }, // Patient #1
+ { "PAT2" }, // Patient #2
+ { "PATR" }, // Patron
+ { "QUEN" }, // Queen Victoria
+ { "WITE" }, // Patient in White
+ { "LUSH" }, // Lush
+ { "DRNK" }, // Drunk
+ { "PROS" }, // Prostitute
+ { "MUDL" }, // Mudlark
+ { "GRIN" }, // Grinder
+ { "BOUN" }, // Bouncer
+ { "RATC" }, // Agnes Ratchet
+ { "ALOY" }, // Aloysius Ratchet
+ { "REAL" }, // Real Estate Agent
+ { "CAND" }, // Candy Clerk
+ { "BEAD" }, // Beadle
+ { "PRUS" }, // Prussian
+ { "ROWB" }, // Mrs. Rowbottom
+ { "MSLJ" }, // Miss Lloyd-Jones
+ { "TPAT" }, // Tavern patron
+ { "USER" }, // User
+ { "TOBY" }, // Toby
+ { "STAT" }, // Stationer
+ { "CLRK" }, // Law Clerk
+ { "CLER" }, // Ministry Clerk
+ { "BATH" }, // Bather
+ { "MAID" }, // Maid
+ { "LADF" }, // Lady Fanshawe
+ { "SIDN" }, // Sidney Ratchet
+ { "BOYO" }, // Boy
+ { "PTR2" }, // Second Patron
+ { "BRIT" }, // Constable Brit
+ { "DROV" } // Wagon Driver
+};
+
+const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE] = {
+ "Sherlock Holmes",
+ "Dr. Watson",
+ "Mme. Hudson",
+ "Stanley Forbes",
+ "Mycroft Holmes",
+ "Wiggins",
+ "Sergent Burns",
+ "Augustus Trimble",
+ "Sergent Daley",
+ "Infirmi?re chef",
+ "Mme. Grace",
+ "Preston McCabe",
+ "Bob Colleran",
+ "Jonas Rigby",
+ "Sergent Roach",
+ "James Dewar",
+ "Sergent Jeremy Duncan",
+ "Inspecteur Gregson",
+ "Inspecteur Lestrade",
+ "Jesse Needhem",
+ "Arthur Fleming",
+ "M. Thomas Pratt",
+ "Mathilda (Tillie) Mason",
+ "Adrian Russell",
+ "Eldridge Whitney",
+ "Hepplethwaite",
+ "Horace Silverbridge",
+ "Sherman",
+ "Maxwell Verner",
+ "Millicent Redding",
+ "Virgil Silverbridge",
+ "George O'Keeffe",
+ "Lord Denys Lawton",
+ "Jenkins",
+ "Jock Mahoney",
+ "Serveur",
+ "Lady Cordelia Lockridge",
+ "Pettigrew",
+ "Sir Avery Fanshawe",
+ "Hodgkins",
+ "Wilbur \"Birdy\" Heywood",
+ "Jacob Farthington",
+ "Philip Bledsoe",
+ "Sidney Fowler",
+ "Professeur Theodore Totman",
+ "Rose Hinchem",
+ "Tallboy",
+ "Ethlebert \"Stitch\" Rumsey",
+ "Charles Freedman",
+ "Nigel Hemmings",
+ "Fairfax Carter",
+ "Wilhelm II",
+ "Wachthund",
+ "Jonathan Wilson",
+ "David Lloyd-Jones",
+ "Edward Hargrove",
+ "Misteray",
+ "Le Lascar",
+ "Oiseau",
+ "Vincent Scarrett",
+ "Alexandra",
+ "Queen Victoria",
+ "John Brown",
+ "Patient",
+ "Patient",
+ "Client",
+ "Queen Victoria",
+ "Patient en blanc",
+ "Ivrogne",
+ "Ivrogne",
+ "Belle femme",
+ "Mudlark",
+ "Broyeur",
+ "Videur",
+ "Agnes Ratchet",
+ "Aloysius Ratchet",
+ "Immobilier",
+ "Gar?on",
+ "Beadle",
+ "Prussian",
+ "Mme. Rowbottom",
+ "Mme Lloyd-Jones",
+ "Tavern Client",
+ "User",
+ "Toby",
+ "Papeterie",
+ "Law Clerc",
+ "Ministry Employ?",
+ "Clint du thermes",
+ "Bonne",
+ "Lady Fanshawe",
+ "Sidney Ratchet",
+ "Gar?on",
+ "Client",
+ "Sergent Brit",
+ "Wagon Driver"
+};
+
+const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE] = {
+ "Sherlock Holmes",
+ "Dr. Watson",
+ "Mrs. Hudson",
+ "Stanley Forbes",
+ "Mycroft Holmes",
+ "Wiggins",
+ "Police Constable Burns",
+ "Augustus Trimble",
+ "Police Constable Daley",
+ "Matron",
+ "Sister Grace",
+ "Preston McCabe",
+ "Bob Colleran",
+ "Jonas Rigby",
+ "Police Constable Roach",
+ "James Dewar",
+ "Sergeant Jeremy Duncan",
+ "Inspector Gregson",
+ "Inspector Lestrade",
+ "Jesse Needhem",
+ "Arthur Fleming",
+ "Mr. Thomas Pratt",
+ "Mathilda (Tillie) Mason",
+ "Adrian Russell",
+ "Eldridge Whitney",
+ "Hepplethwaite",
+ "Horace Silverbridge",
+ "Old Sherman",
+ "Maxwell Verner",
+ "Millicent Redding",
+ "Virgil Silverbridge",
+ "George O'Keeffe",
+ "Lord Denys Lawton",
+ "Jenkins",
+ "Jock Mahoney",
+ "Bartender",
+ "Lady Cordelia Lockridge",
+ "Pettigrew",
+ "Sir Avery Fanshawe",
+ "Hodgkins",
+ "Wilbur \"Birdy\" Heywood",
+ "Jacob Farthington",
+ "Philip Bledsoe",
+ "Sidney Fowler",
+ "Professor Theodore Totman",
+ "Rose Hinchem",
+ "Tallboy",
+ "Ethlebert \"Stitch\" Rumsey",
+ "Charles Freedman",
+ "Nigel Hemmings",
+ "Fairfax Carter",
+ "Wilhelm II",
+ "Wachthund",
+ "Jonathan Wilson",
+ "David Lloyd-Jones",
+ "Edward Hargrove",
+ "Misteray",
+ "The Lascar",
+ "Parrot",
+ "Vincent Scarrett",
+ "Alexandra",
+ "Queen Victoria",
+ "John Brown",
+ "A Patient",
+ "A Patient",
+ "Patron",
+ "Queen Victoria",
+ "Patient in white",
+ "Lush",
+ "Drunk",
+ "Prostitute",
+ "Mudlark",
+ "Grinder",
+ "Bouncer",
+ "Agnes Ratchet",
+ "Aloysius Ratchet",
+ "Real Estate Agent",
+ "Candy Clerk",
+ "Beadle",
+ "Prussian",
+ "Mrs. Rowbottom",
+ "Miss Lloyd-Jones",
+ "Tavern patron",
+ "User",
+ "Toby",
+ "Stationer",
+ "Law Clerk",
+ "Ministry Clerk",
+ "Bather",
+ "Maid",
+ "Lady Fanshawe",
+ "Sidney Ratchet",
+ "Boy",
+ "Patron",
+ "Constable Brit",
+ "Wagon Driver"
+};
+
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_resources.h b/engines/sherlock/tattoo/tattoo_resources.h
new file mode 100644
index 0000000000..b706d90f2d
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_resources.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_RESOURCES_H
+#define SHERLOCK_TATTOO_RESOURCES_H
+
+#include "common/scummsys.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define TATTOO_MAX_PEOPLE 96
+
+extern const char PORTRAITS[TATTOO_MAX_PEOPLE][5];
+extern const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE];
+extern const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE];
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp
new file mode 100644
index 0000000000..ba462ca255
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_scene.cpp
@@ -0,0 +1,821 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/events.h"
+#include "sherlock/people.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+struct ShapeEntry {
+ Object *_shape;
+ TattooPerson *_person;
+ bool _isAnimation;
+ int _yp;
+
+ ShapeEntry(TattooPerson *person, int yp) : _shape(nullptr), _person(person), _yp(yp), _isAnimation(false) {}
+ ShapeEntry(Object *shape, int yp) : _shape(shape), _person(nullptr), _yp(yp), _isAnimation(false) {}
+ ShapeEntry(int yp) : _shape(nullptr), _person(nullptr), _yp(yp), _isAnimation(true) {}
+};
+typedef Common::List<ShapeEntry> ShapeList;
+
+static bool sortImagesY(const ShapeEntry &s1, const ShapeEntry &s2) {
+ return s1._yp <= s2._yp;
+}
+
+/*----------------------------------------------------------------*/
+
+TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm) {
+ _labTableScene = false;
+}
+
+bool TattooScene::loadScene(const Common::String &filename) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ Music &music = *_vm->_music;
+ Talk &talk = *_vm->_talk;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ // If we're going to the first game scene after the intro sequence, flag it as finished
+ if (vm._runningProlog && _currentScene == STARTING_GAME_SCENE) {
+ vm._runningProlog = false;
+ events.showCursor();
+ talk._talkToAbort = false;
+ }
+
+ // Check if it's a scene we need to keep trakc track of how many times we've visited
+ for (int idx = (int)_sceneTripCounters.size() - 1; idx >= 0; --idx) {
+ if (_sceneTripCounters[idx]._sceneNumber == _currentScene) {
+ if (--_sceneTripCounters[idx]._numTimes == 0) {
+ _vm->setFlags(_sceneTripCounters[idx]._flag);
+ _sceneTripCounters.remove_at(idx);
+ }
+ }
+ }
+
+ // Set the NPC paths for the scene
+ setNPCPath(0);
+
+ // Handle loading music for the scene
+ if (music._musicOn) {
+ if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3)
+ music._nextSongName = Common::String::format("res%02d", _currentScene);
+
+ // If it's a new song, then start it up
+ if (music._currentSongName.compareToIgnoreCase(music._nextSongName)) {
+ if (music.loadSong(music._nextSongName)) {
+ music.setMIDIVolume(music._musicVolume);
+ if (music._musicOn)
+ music.startSong();
+ }
+ }
+ }
+
+ bool result = Scene::loadScene(filename);
+
+ if (_currentScene != STARTING_INTRO_SCENE) {
+ // Set the menu/ui mode and whether we're in a lab table close-up scene
+ _labTableScene = _currentScene > 91 && _currentScene < 100;
+ ui._menuMode = _labTableScene ? LAB_MODE : STD_MODE;
+
+ if (_labTableScene)
+ ui._labWidget.summonWindow();
+ }
+
+ return result;
+}
+
+void TattooScene::drawAllShapes() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ ShapeList shapeList;
+
+ // Draw all objects and animations that are set to behind
+ screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT));
+
+ // Draw all active shapes which are behind the person
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE && obj._misc == BEHIND) {
+ if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD)
+ screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position);
+ else
+ screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal);
+ }
+ }
+
+ // Draw the animation if it is behind the person
+ if (_activeCAnim.active() && _activeCAnim._zPlacement == BEHIND)
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
+ (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal);
+
+ screen.resetDisplayBounds();
+
+ // Queue drawing of all objects that are set to NORMAL.
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE && (obj._misc == NORMAL_BEHIND || obj._misc == NORMAL_FORWARD)) {
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->_offset.y +
+ obj._imageFrame->_height));
+ else
+ shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->sDrawYOffset(obj._scaleVal) +
+ obj._imageFrame->sDrawYSize(obj._scaleVal)));
+ }
+ }
+
+ // Queue drawing the animation if it is NORMAL and can fall in front of, or behind the people
+ if (_activeCAnim.active() && (_activeCAnim._zPlacement == NORMAL_BEHIND || _activeCAnim._zPlacement == NORMAL_FORWARD)) {
+ if (_activeCAnim._scaleVal == SCALE_THRESHOLD)
+ shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame._offset.y +
+ _activeCAnim._imageFrame._height));
+ else
+ shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame.sDrawYOffset(_activeCAnim._scaleVal) +
+ _activeCAnim._imageFrame.sDrawYSize(_activeCAnim._scaleVal)));
+ }
+
+ // Queue all active characters for drawing
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER && people[idx]._walkLoaded)
+ shapeList.push_back(ShapeEntry(&people[idx], people[idx]._position.y / FIXED_INT_MULTIPLIER));
+ }
+
+ // Sort the list
+ Common::sort(shapeList.begin(), shapeList.end(), sortImagesY);
+
+ // Draw the list of shapes in order
+ for (ShapeList::iterator i = shapeList.begin(); i != shapeList.end(); ++i) {
+ ShapeEntry &se = *i;
+
+ if (se._shape) {
+ // it's a bg shape
+ if (se._shape->_quickDraw && se._shape->_scaleVal == SCALE_THRESHOLD)
+ screen._backBuffer1.blitFrom(*se._shape->_imageFrame, se._shape->_position);
+ else
+ screen._backBuffer1.transBlitFrom(*se._shape->_imageFrame, se._shape->_position,
+ se._shape->_flags & OBJ_FLIPPED, 0, se._shape->_scaleVal);
+ } else if (se._isAnimation) {
+ // It's an active animation
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position,
+ (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal);
+ } else {
+ // Drawing person
+ TattooPerson &p = *se._person;
+
+ p._tempX = p._position.x / FIXED_INT_MULTIPLIER;
+ p._tempScaleVal = getScaleVal(p._position);
+ Common::Point adjust = p._adjust;
+
+ if (p._tempScaleVal == SCALE_THRESHOLD) {
+ p._tempX += adjust.x;
+ screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
+ - p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal);
+ } else {
+ if (adjust.x) {
+ if (!p._tempScaleVal)
+ ++p._tempScaleVal;
+
+ if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.x)
+ --adjust.x;
+
+ adjust.x = adjust.x * SCALE_THRESHOLD / p._tempScaleVal;
+
+ if (p._tempScaleVal >= SCALE_THRESHOLD)
+ ++adjust.x;
+ p._tempX += adjust.x;
+ }
+
+ if (adjust.y) {
+ if (!p._tempScaleVal)
+ p._tempScaleVal++;
+
+ if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.y)
+ --adjust.y;
+
+ adjust.y = adjust.y * SCALE_THRESHOLD / p._tempScaleVal;
+
+ if (p._tempScaleVal >= SCALE_THRESHOLD)
+ ++adjust.y;
+ }
+
+ screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
+ - p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal);
+ }
+ }
+ }
+
+ // Draw all objects & canimations that are set to FORWARD.
+ // Draw all static and active shapes that are FORWARD
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE && obj._misc == FORWARD) {
+ if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD)
+ screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position);
+ else
+ screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal);
+ }
+ }
+
+ // Draw the canimation if it is set as FORWARD
+ if (_activeCAnim.active() && _activeCAnim._zPlacement == FORWARD)
+ screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal);
+
+ // Draw all NO_SHAPE shapes which have their flag bits clear
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+ if (obj._type == NO_SHAPE && (obj._flags & 1) == 0)
+ screen._backBuffer1.fillRect(obj.getNoShapeBounds(), 15);
+ }
+}
+
+void TattooScene::paletteLoaded() {
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ ui.setupBGArea(screen._cMap);
+ ui.initScrollVars();
+}
+
+void TattooScene::checkBgShapes() {
+ // Call the base scene method to handle bg shapes
+ Scene::checkBgShapes();
+
+ // Check for any active playing animation
+ if (_activeCAnim.active() && _activeCAnim._zPlacement != REMOVE) {
+ switch (_activeCAnim._flags & 3) {
+ case 0:
+ _activeCAnim._zPlacement = BEHIND;
+ break;
+ case 1:
+ _activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame._frame.h - 1)) ?
+ NORMAL_FORWARD : NORMAL_BEHIND;
+ break;
+ case 2:
+ _activeCAnim._zPlacement = FORWARD;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void TattooScene::doBgAnimCheckCursor() {
+ Events &events = *_vm->_events;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+
+ // If we're in Look Mode, make sure the cursor is the magnifying glass
+ if (ui._menuMode == LOOK_MODE && events.getCursor() != MAGNIFY)
+ events.setCursor(MAGNIFY);
+
+ // See if the mouse is over any of the arrow zones, and if so, change the cursor to the correct
+ // arrow cursor indicating the direcetion of the exit
+ if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) {
+ CursorId cursorId = ARROW;
+
+ if (ui._menuMode == STD_MODE && ui._arrowZone != -1 && _currentScene != 90) {
+ for (uint idx = 0; idx < _exits.size(); ++idx) {
+ Exit &exit = _exits[idx];
+ if (exit.contains(mousePos))
+ cursorId = (CursorId)(exit._image + EXIT_ZONES_START);
+ }
+ }
+
+ events.setCursor(cursorId);
+ } else {
+ events.animateCursorIfNeeded();
+ }
+}
+
+void TattooScene::doBgAnim() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui);
+
+ doBgAnimCheckCursor();
+
+ talk._talkToAbort = false;
+
+ // Check the characters and sprites for updates
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER)
+ people[idx].checkSprite();
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE)
+ _bgShapes[idx].checkObject();
+ }
+
+ // If one of the objects has signalled a call to a talk file, to go to another scene, exit immediately
+ if (_goToScene != -1)
+ return;
+
+ // Erase any affected background areas
+ ui.doBgAnimEraseBackground();
+
+ doBgAnimUpdateBgObjectsAndAnim();
+
+ doBgAnimDrawSprites();
+
+ ui.drawInterface();
+
+ if (vm._creditsActive)
+ vm.blitCredits();
+
+ if (!vm._fastMode)
+ events.wait(3);
+
+ if (screen._flushScreen) {
+ screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+ screen._flushScreen = false;
+ }
+
+ screen._flushScreen = false;
+ _doBgAnimDone = true;
+ ui._drawMenu = false;
+
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._updateNPCPath)
+ people[idx].updateNPC();
+ }
+}
+
+void TattooScene::doBgAnimUpdateBgObjectsAndAnim() {
+ People &people = *_vm->_people;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+ if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE)
+ obj.adjustObject();
+ }
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER)
+ people[idx].adjustSprite();
+ }
+
+ // Flag the bg shapes which need to be redrawn
+ checkBgShapes();
+ drawAllShapes();
+
+ ui.drawMaskArea(true);
+}
+
+void TattooScene::updateBackground() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ Scene::updateBackground();
+
+ ui.drawMaskArea(false);
+
+ screen._flushScreen = true;
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ TattooPerson &p = people[idx];
+
+ if (p._type != INVALID) {
+ if (_goToScene == -1 || _cAnim.size() == 0) {
+ if (p._type == REMOVE) {
+ screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y);
+ p._type = INVALID;
+ } else {
+ if (p._tempScaleVal == SCALE_THRESHOLD) {
+ screen.flushImage(p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER
+ - p._imageFrame->_width), &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y);
+ } else {
+ int ts = p._imageFrame->sDrawYSize(p._tempScaleVal);
+ int ty = p._position.y / FIXED_INT_MULTIPLIER - ts;
+ screen.flushScaleImage(p._imageFrame, Common::Point(p._tempX, ty),
+ &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y, p._tempScaleVal);
+ }
+ }
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) {
+ if (_goToScene == -1) {
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y);
+ else
+ screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
+
+ if (obj._type == REMOVE)
+ obj._type = INVALID;
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (_goToScene == -1) {
+ if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
+ screen.slamRect(obj.getNoShapeBounds());
+ screen.slamRect(obj.getOldBounds());
+ } else if (obj._type == HIDE_SHAPE) {
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y);
+ else
+ screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
+ obj._type = HIDDEN;
+ }
+ }
+ }
+
+ screen._flushScreen = false;
+}
+
+void TattooScene::doBgAnimDrawSprites() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
+ TattooPerson &person = people[idx];
+
+ if (person._type != INVALID) {
+ if (_goToScene == -1 || _cAnim.size() == 0) {
+ if (person._type == REMOVE) {
+ screen.slamRect(person.getOldBounds());
+ person._type = INVALID;
+ } else {
+ if (person._tempScaleVal == SCALE_THRESHOLD) {
+ screen.flushImage(person._imageFrame, Common::Point(person._tempX, person._position.y / FIXED_INT_MULTIPLIER
+ - person.frameHeight()), &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y);
+ } else {
+ int ts = person._imageFrame->sDrawYSize(person._tempScaleVal);
+ int ty = person._position.y / FIXED_INT_MULTIPLIER - ts;
+ screen.flushScaleImage(person._imageFrame, Common::Point(person._tempX, ty),
+ &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y, person._tempScaleVal);
+ }
+ }
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) {
+ if (_goToScene == -1) {
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y);
+ else
+ screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
+
+ if (obj._type == REMOVE)
+ obj._type = INVALID;
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
+ Object &obj = _bgShapes[idx];
+
+ if (_goToScene == -1) {
+ if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
+ screen.slamRect(obj.getNoShapeBounds());
+ screen.slamRect(obj.getOldBounds());
+ } else if (obj._type == HIDE_SHAPE) {
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y);
+ else
+ screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y,
+ &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal);
+ obj._type = HIDDEN;
+ }
+ }
+ }
+
+ if (_activeCAnim.active() || _activeCAnim._zPlacement == REMOVE) {
+ if (_activeCAnim._zPlacement != REMOVE) {
+ screen.flushImage(&_activeCAnim._imageFrame, _activeCAnim._position, _activeCAnim._oldBounds, _activeCAnim._scaleVal);
+ } else {
+ screen.slamRect(_activeCAnim._removeBounds);
+ _activeCAnim._removeBounds = Common::Rect(0, 0, 0, 0);
+ _activeCAnim._zPlacement = -1; // Reset _zPlacement so we don't REMOVE again
+ }
+ }
+}
+
+int TattooScene::getScaleVal(const Point32 &pt) {
+ bool found = false;
+ int result = SCALE_THRESHOLD;
+ Common::Point pos(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER);
+
+ for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) {
+ ScaleZone &sz = _scaleZones[idx];
+ if (sz.contains(pos)) {
+ int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber;
+ result = 25600L / n;
+ // CHECKME: Shouldn't we set 'found' at this place?
+ }
+ }
+
+ // If it wasn't found, we may be off screen to the left or right, so find the scale zone
+ // that would apply to the y val passed in disregarding the x
+ if (!found) {
+ for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) {
+ ScaleZone &sz = _scaleZones[idx];
+ if (pos.y >= sz.top && pos.y < sz.bottom) {
+ int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber;
+ result = 25600L / n;
+ }
+ }
+ }
+
+ return result;
+}
+
+int TattooScene::startCAnim(int cAnimNum, int playRate) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Resources &res = *_vm->_res;
+ Talk &talk = *_vm->_talk;
+ UserInterface &ui = *_vm->_ui;
+
+ // Exit immediately if the anim number is out of range, or the anim doesn't have a position specified
+ if (cAnimNum < 0 || cAnimNum >= (int)_cAnim.size() || _cAnim[cAnimNum]._position.x == -1)
+ // Return out of range error
+ return -1;
+
+ // Get the co-ordinates that the Player & NPC #1 must walk to and end on
+ CAnim &cAnim = _cAnim[cAnimNum];
+ PositionFacing goto1 = cAnim._goto[0];
+ PositionFacing goto2 = cAnim._goto[1];
+ PositionFacing teleport1 = cAnim._teleport[0];
+ PositionFacing teleport2 = cAnim._teleport[1];
+
+ // See if the Player must walk to a position before the animation starts
+ SpriteType savedPlayerType = people[HOLMES]._type;
+ if (goto1.x != -1 && people[HOLMES]._type == CHARACTER) {
+ if (people[HOLMES]._position != goto1)
+ people[HOLMES].walkToCoords(goto1, goto1._facing);
+ }
+
+ if (talk._talkToAbort)
+ return 1;
+
+ // See if NPC #1 must walk to a position before the animation starts
+ SpriteType savedNPCType = people[WATSON]._type;
+ if (goto2.x != -1 && people[WATSON]._type == CHARACTER) {
+ if (people[WATSON]._position != goto2)
+ people[WATSON].walkToCoords(goto2, goto2._facing);
+ }
+
+ if (talk._talkToAbort)
+ return 1;
+
+ // Turn the player (and NPC #1 if neccessary) off before running the canimation
+ if (teleport1.x != -1 && savedPlayerType == CHARACTER)
+ people[HOLMES]._type = REMOVE;
+
+ if (teleport2.x != -1 && savedNPCType == CHARACTER)
+ people[WATSON]._type = REMOVE;
+
+ if (ui._windowOpen)
+ ui.banishWindow();
+
+ //_activeCAnim._filesize = cAnim._size;
+
+ // Open up the room resource file and get the data for the animation
+ Common::SeekableReadStream *stream = res.load(_roomFilename);
+ stream->seek(44 + cAnimNum * 4);
+ stream->seek(stream->readUint32LE());
+ Common::SeekableReadStream *animStream = stream->readStream(cAnim._dataSize);
+ delete stream;
+
+ // Set up the active animation
+ _activeCAnim._position = cAnim._position;
+ _activeCAnim._oldBounds = Common::Rect(0, 0, 0, 0);
+ _activeCAnim._flags = cAnim._flags;
+ _activeCAnim._scaleVal = cAnim._scaleVal;
+ _activeCAnim._zPlacement = 0;
+
+ _activeCAnim.load(animStream, _compressed);
+
+ while (_activeCAnim.active() && !_vm->shouldQuit()) {
+ // Get the next frame
+ _activeCAnim.getNextFrame();
+
+ // Draw the frame
+ doBgAnim();
+
+ // Check for Escape key being pressed to abort animation
+ events.pollEvents();
+ if (events.kbHit()) {
+ Common::KeyState keyState = events.getKey();
+
+ if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) {
+ _vm->setFlags(-76);
+ _vm->setFlags(396);
+ _goToScene = STARTING_GAME_SCENE;
+ talk._talkToAbort = true;
+ _activeCAnim.close();
+ }
+ }
+ }
+
+ // Turn the people back on
+ people[HOLMES]._type = savedPlayerType;
+ if (teleport2.x != -1)
+ people[WATSON]._type = savedNPCType;
+
+ // Teleport the Player to the ending coordinates if necessary
+ if (teleport1.x != -1 && savedPlayerType == CHARACTER) {
+ people[HOLMES]._position = teleport1;
+ people[HOLMES]._sequenceNumber = teleport1._facing;
+ people[HOLMES].gotoStand();
+ }
+
+ // Teleport Watson to the ending coordinates if necessary
+ if (teleport2.x != -1 && savedNPCType == CHARACTER) {
+ people[WATSON]._position = teleport2;
+ people[WATSON]._sequenceNumber = teleport2._facing;
+ people[WATSON].gotoStand();
+ }
+
+ // Flag the Canimation to be cleared
+ _activeCAnim._zPlacement = REMOVE;
+ _activeCAnim._removeBounds = _activeCAnim._oldBounds;
+
+ // Free up the animation
+ _activeCAnim.close();
+
+ return 1;
+}
+
+void TattooScene::setNPCPath(int npc) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ SaveManager &saves = *_vm->_saves;
+ Talk &talk = *_vm->_talk;
+
+ // Don't do initial scene setup if a savegame has just been loaded
+ if (saves._justLoaded)
+ return;
+
+ people[npc].clearNPC();
+ people[npc]._name = Common::String::format("WATS%.2dA", _currentScene);
+
+ // If we're in the middle of a script that will continue once the scene is loaded,
+ // return without calling the path script
+ if (talk._scriptMoreFlag == 1 || talk._scriptMoreFlag == 3)
+ return;
+
+ // Turn off all the NPCs, since the talk script will turn them back on as needed
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx)
+ people[idx]._type = INVALID;
+
+ // Call the path script for the scene
+ Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene);
+ talk.talkTo(pathFile);
+}
+
+int TattooScene::findBgShape(const Common::Point &pt) {
+ People &people = *_vm->_people;
+
+ if (!_doBgAnimDone)
+ // New frame hasn't been drawn yet
+ return -1;
+
+ int result = Scene::findBgShape(pt);
+ if (result == -1) {
+ if (_labTableScene) {
+ // Check for SOLID objects in the lab scene
+ for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
+ Object &o = _bgShapes[idx];
+ if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN && o._aType == SOLID) {
+ if (o.getNewBounds().contains(pt))
+ return idx;
+ }
+ }
+ }
+
+ // No shape found, so check whether a character is highlighted
+ for (int idx = 1; idx < MAX_CHARACTERS && result == -1; ++idx) {
+ Person &person = people[idx];
+
+ if (person._type == CHARACTER) {
+ int scaleVal = getScaleVal(person._position);
+ Common::Rect charRect;
+
+ if (scaleVal == SCALE_THRESHOLD)
+ charRect = Common::Rect(person.frameWidth(), person.frameHeight());
+ else
+ charRect = Common::Rect(person._imageFrame->sDrawXSize(scaleVal), person._imageFrame->sDrawYSize(scaleVal));
+ charRect.moveTo(person._position.x / FIXED_INT_MULTIPLIER, person._position.y / FIXED_INT_MULTIPLIER
+ - charRect.height());
+
+ if (charRect.contains(pt))
+ result = 1000 + idx;
+ }
+ }
+ }
+
+ return result;
+}
+
+void TattooScene::synchronize(Serializer &s) {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Scene::synchronize(s);
+
+ if (s.isLoading())
+ vm._runningProlog = false;
+}
+
+int TattooScene::closestZone(const Common::Point &pt) {
+ int zone = -1;
+ int dist = 9999;
+ int d;
+
+ for (uint idx = 0; idx < _zones.size(); ++idx) {
+ Common::Rect &r = _zones[idx];
+
+ // Check the distance from the point to the center of the zone
+ d = ABS(r.left + (r.width() / 2) - pt.x) + ABS(r.top + (r.height() / 2) - pt.y);
+ if (d < dist) {
+ dist = d;
+ zone = idx;
+ }
+
+ // Check the distance from the point to the upper left of the zone
+ d = ABS((int)(r.left - pt.x)) + ABS((int)(r.top - pt.y));
+ if (d < dist)
+ {
+ dist = d;
+ zone = idx;
+ }
+
+ // Check the distance from the point to the upper right of the zone
+ d = ABS(r.left + r.width() - pt.x) + ABS(r.top - pt.y);
+ if (d < dist) {
+ dist = d;
+ zone = idx;
+ }
+
+ // Check the distance from the point to the lower left of the zone
+ d = ABS(r.left - pt.x) + ABS(r.top + r.height() - pt.y);
+ if (d < dist) {
+ dist = d;
+ zone = idx;
+ }
+
+ // Check the distance from the point to the lower right of the zone
+ d = ABS(r.left + r.width() - pt.x) + ABS(r.top + r.height() - pt.y);
+ if (d < dist) {
+ dist = d;
+ zone = idx;
+ }
+ }
+
+ return zone;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_scene.h b/engines/sherlock/tattoo/tattoo_scene.h
new file mode 100644
index 0000000000..c432849bed
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_scene.h
@@ -0,0 +1,147 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_SCENE_H
+#define SHERLOCK_TATTOO_SCENE_H
+
+#include "common/scummsys.h"
+#include "sherlock/scene.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+enum {
+ STARTING_GAME_SCENE = 1, STARTING_INTRO_SCENE = 91, OVERHEAD_MAP2 = 90, OVERHEAD_MAP = 100
+};
+
+struct SceneTripEntry {
+ int _flag;
+ int _sceneNumber;
+ int _numTimes;
+
+ SceneTripEntry() : _flag(0), _sceneNumber(0), _numTimes(0) {}
+ SceneTripEntry(int flag, int sceneNumber, int numTimes) : _flag(flag),
+ _sceneNumber(sceneNumber), _numTimes(numTimes) {}
+};
+
+class TattooScene : public Scene {
+private:
+ void doBgAnimCheckCursor();
+
+ /**
+ * Update the background objects and canimations as part of doBgAnim
+ */
+ void doBgAnimUpdateBgObjectsAndAnim();
+
+ void doBgAnimDrawSprites();
+
+ /**
+ * Resets the NPC path information when entering a new scene.
+ * @remarks The default talk file for the given NPC is set to WATS##A, where ## is
+ * the scene number being entered
+ */
+ void setNPCPath(int npc);
+protected:
+ /**
+ * Loads the data associated for a given scene. The room resource file's format is:
+ * BGHEADER: Holds an index for the rest of the file
+ * STRUCTS: The objects for the scene
+ * IMAGES: The graphic information for the structures
+ *
+ * The _misc field of the structures contains the number of the graphic image
+ * that it should point to after loading; _misc is then set to 0.
+ */
+ virtual bool loadScene(const Common::String &filename);
+
+ /**
+ * Checks all the background shapes. If a background shape is animating,
+ * it will flag it as needing to be drawn. If a non-animating shape is
+ * colliding with another shape, it will also flag it as needing drawing
+ */
+ virtual void checkBgShapes();
+
+ /**
+ * Draw all the shapes, people and NPCs in the correct order
+ */
+ virtual void drawAllShapes();
+
+ /**
+ * Called by loadScene when the palette is loaded for Rose Tattoo
+ */
+ virtual void paletteLoaded();
+
+ /**
+ * Synchronize the data for a savegame
+ */
+ virtual void synchronize(Serializer &s);
+
+ /**
+ * Returns the index of the closest zone to a given point.
+ */
+ virtual int closestZone(const Common::Point &pt);
+public:
+ StreamingImageFile _activeCAnim;
+ Common::Array<SceneTripEntry> _sceneTripCounters;
+ bool _labTableScene;
+public:
+ TattooScene(SherlockEngine *vm);
+
+ /**
+ * Returns the scale value for the passed co-ordinates. This is taken from the scene's
+ * scale zones, interpolating inbetween the top and bottom values of the zones as needed
+ */
+ int getScaleVal(const Point32 &pt);
+
+ /**
+ * Draw all objects and characters.
+ */
+ virtual void doBgAnim();
+
+ /**
+ * Update the screen back buffer with all of the scene objects which need
+ * to be drawn
+ */
+ virtual void updateBackground();
+
+ /**
+ * Attempt to start a canimation sequence. It will load the requisite graphics, and
+ * then copy the canim object into the _canimShapes array to start the animation.
+ *
+ * @param cAnimNum The canim object within the current scene
+ * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc.
+ * A negative playRate can also be specified to play the animation in reverse
+ */
+ virtual int startCAnim(int cAnimNum, int playRate = 1);
+
+ /**
+ * Attempts to find a background shape within the passed bounds. If found,
+ * it will return the shape number, or -1 on failure.
+ */
+ virtual int findBgShape(const Common::Point &pt);
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_talk.cpp b/engines/sherlock/tattoo/tattoo_talk.cpp
new file mode 100644
index 0000000000..dbeeaf8918
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_talk.cpp
@@ -0,0 +1,882 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_talk.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/sherlock.h"
+#include "sherlock/screen.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+static const uint8 DIRECTION_CONVERSION[] = {
+ WALK_RIGHT, WALK_DOWN, WALK_LEFT, WALK_UP, STOP_RIGHT, STOP_DOWN, STOP_LEFT, STOP_UP,
+ WALK_UPRIGHT, WALK_DOWNRIGHT, WALK_UPLEFT, WALK_DOWNLEFT, STOP_UPRIGHT, STOP_UPLEFT,
+ STOP_DOWNRIGHT, STOP_DOWNLEFT
+};
+
+const byte TATTOO_OPCODES[] = {
+ 170, // OP_SWITCH_SPEAKER
+ 171, // OP_RUN_CANIMATION
+ 0, // OP_ASSIGN_PORTRAIT_LOCATION
+ 173, // OP_PAUSE
+ 0, // OP_REMOVE_PORTRAIT
+ 0, // OP_CLEAR_WINDOW
+ 176, // OP_ADJUST_OBJ_SEQUENCE
+ 177, // OP_WALK_HOlMES_TO_COORDS
+ 178, // OP_PAUSE_WITHOUT_CONTROL
+ 179, // OP_BANISH_WINDOW
+ 0, // OP_SUMMON_WINDOW
+ 181, // OP_SET_FLAG
+ 0, // OP_SFX_COMMAND
+ 183, // OP_TOGGLE_OBJECT
+ 184, // OP_STEALTH_MODE_ACTIVE
+ 0, // OP_IF_STATEMENT
+ 0, // OP_ELSE_STATEMENT
+ 0, // OP_END_IF_STATEMENT
+ 188, // OP_STEALTH_MODE_DEACTIVATE
+ 189, // OP_TURN_HOLMES_OFF
+ 190, // OP_TURN_HOLMES_ON
+ 191, // OP_GOTO_SCENE
+ 0, // OP_PLAY_PROLOGUE
+ 193, // OP_ADD_ITEM_TO_INVENTORY
+ 194, // OP_SET_OBJECT
+ 172, // OP_CALL_TALK_FILE
+ 0, // OP_MOVE_MOUSE
+ 0, // OP_DISPLAY_INFO_LINE
+ 0, // OP_CLEAR_INFO_LINE
+ 199, // OP_WALK_TO_CANIMATION
+ 200, // OP_REMOVE_ITEM_FROM_INVENTORY
+ 201, // OP_ENABLE_END_KEY
+ 202, // OP_DISABLE_END_KEY
+ 203, // OP_END_TEXT_WINDOW
+ 174, // OP_MOUSE_ON_OFF
+ 175, // OP_SET_WALK_CONTROL
+ 180, // OP_SET_TALK_SEQUENCE
+ 182, // OP_PLAY_SONG
+ 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM
+ 192, // OP_SET_NPC_PATH_DEST
+ 195, // OP_NEXT_SONG
+ 196, // OP_SET_NPC_PATH_PAUSE
+ 197, // OP_PASSWORD
+ 198, // OP_SET_SCENE_ENTRY_FLAG
+ 185, // OP_WALK_NPC_TO_CANIM
+ 186, // OP_WALK_NPC_TO_COORDS
+ 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS
+ 205, // OP_SET_NPC_TALK_FILE
+ 206, // OP_TURN_NPC_OFF
+ 207, // OP_TURN_NPC_ON
+ 208, // OP_NPC_DESC_ON_OFF
+ 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES
+ 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES
+ 211, // OP_ENABLE_TALK_INTERRUPTS
+ 212, // OP_DISABLE_TALK_INTERRUPTS
+ 213, // OP_SET_NPC_INFO_LINE
+ 214, // OP_SET_NPC_POSITION
+ 215, // OP_NPC_PATH_LABEL
+ 216, // OP_PATH_GOTO_LABEL
+ 217, // OP_PATH_IF_FLAG_GOTO_LABEL
+ 218, // OP_NPC_WALK_GRAPHICS
+ 220, // OP_NPC_VERB
+ 221, // OP_NPC_VERB_CANIM
+ 222, // OP_NPC_VERB_SCRIPT
+ 224, // OP_RESTORE_PEOPLE_SEQUENCE
+ 226, // OP_NPC_VERB_TARGET
+ 227, // OP_TURN_SOUNDS_OFF
+ 225 // OP_NULL
+};
+
+/*----------------------------------------------------------------*/
+
+TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm), _talkWidget(vm) {
+ static OpcodeMethod OPCODE_METHODS[] = {
+ (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker,
+
+ (OpcodeMethod)&TattooTalk::cmdRunCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdCallTalkFile,
+ (OpcodeMethod)&TattooTalk::cmdPause,
+ (OpcodeMethod)&TattooTalk::cmdMouseOnOff,
+ (OpcodeMethod)&TattooTalk::cmdSetWalkControl,
+ (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence,
+ (OpcodeMethod)&TattooTalk::cmdWalkHolmesToCoords,
+ (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl,
+ (OpcodeMethod)&TattooTalk::cmdBanishWindow,
+ (OpcodeMethod)&TattooTalk::cmdSetTalkSequence,
+
+ (OpcodeMethod)&TattooTalk::cmdSetFlag,
+ (OpcodeMethod)&TattooTalk::cmdPlaySong,
+ (OpcodeMethod)&TattooTalk::cmdToggleObject,
+ (OpcodeMethod)&TattooTalk::cmdStealthModeActivate,
+ (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate,
+ (OpcodeMethod)&TattooTalk::cmdHolmesOff,
+ (OpcodeMethod)&TattooTalk::cmdHolmesOn,
+
+ (OpcodeMethod)&TattooTalk::cmdGotoScene,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest,
+ (OpcodeMethod)&TattooTalk::cmdAddItemToInventory,
+ (OpcodeMethod)&TattooTalk::cmdSetObject,
+ (OpcodeMethod)&TattooTalk::cmdNextSong,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause,
+ (OpcodeMethod)&TattooTalk::cmdPassword,
+ (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag,
+ (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory,
+
+ (OpcodeMethod)&TattooTalk::cmdEnableEndKey,
+ (OpcodeMethod)&TattooTalk::cmdDisableEndKey,
+ (OpcodeMethod)&TattooTalk::cmdEndTextWindow,
+ (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCOff,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCOn,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes,
+
+ (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable,
+ (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCPosition,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelSet,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto,
+ (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics,
+ nullptr,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerb,
+
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript,
+ nullptr,
+ (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence,
+ (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget,
+ (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff
+ };
+
+ _opcodes = TATTOO_OPCODES;
+ _opcodeTable = OPCODE_METHODS;
+}
+
+void TattooTalk::talkInterface(const byte *&str) {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ const byte *s = str;
+
+ // Move to past the end of the text string
+ _charCount = 0;
+ while ((*str < TATTOO_OPCODES[0] || *str == TATTOO_OPCODES[OP_NULL]) && *str) {
+ ++_charCount;
+ ++str;
+ }
+
+ // Display the text window
+// ui.banishWindow();
+ ui._textWidget.load(Common::String((const char *)s, (const char *)str), _speaker);
+ ui._textWidget.summonWindow();
+ _wait = true;
+}
+
+void TattooTalk::showTalk() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ _sequenceStack.clear();
+ people.setListenSequence(_talkTo, 129);
+
+ _talkWidget.load();
+ _talkWidget.summonWindow();
+ _talkWidget.refresh();
+
+ if (ui._menuMode != MESSAGE_MODE)
+ ui._menuMode = TALK_MODE;
+}
+
+OpcodeReturn TattooTalk::cmdSwitchSpeaker(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Screen &screen = *_vm->_screen;
+ UserInterface &ui = *_vm->_ui;
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ ui.clearWindow();
+
+ _yp = screen.fontHeight() + 11;
+ _charCount = _line = 0;
+
+ people.setListenSequence(_speaker, 129);
+ _speaker = *++str - 1;
+ ++str;
+
+ people.setTalkSequence(_speaker, 1);
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) {
+ Events &events = *_vm->_events;
+ bool mouseOn = *++str == 2;
+ if (mouseOn)
+ events.showCursor();
+ else
+ events.hideCursor();
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkHolmesToCoords(const byte *&str) {
+ People &people = *_vm->_people;
+ ++str;
+
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ // Negative X
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+
+ people[HOLMES].walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
+ DIRECTION_CONVERSION[str[4] - 1]);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 4;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdGotoScene(const byte *&str) {
+ Map &map = *_vm->_map;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ scene._goToScene = str[1] - 1;
+
+ if (scene._goToScene != OVERHEAD_MAP) {
+ // Not going to the map overview
+ map._oldCharPoint = scene._goToScene;
+
+ // Run a canimation?
+ if (str[2] > 100) {
+ people._savedPos = PositionFacing(160, 100, str[2]);
+ } else {
+ int32 posX = (str[3] - 1) * 256 + str[4] - 1;
+ if (posX > 16384)
+ posX = -1 * (posX - 16384);
+ int32 posY = (str[5] - 1) * 256 + str[6] - 1;
+ people._savedPos = PositionFacing(posX, posY, str[2] - 1);
+ }
+
+ _scriptMoreFlag = 1;
+ }
+
+ str += 7;
+ if (scene._goToScene != OVERHEAD_MAP)
+ _scriptSaveIndex = str - _scriptStart;
+
+ _endStr = true;
+ _wait = 0;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) {
+ Music &music = *_vm->_music;
+
+ // Get the name of the next song to play
+ ++str;
+ music._nextSongName = "";
+ for (int idx = 0; idx < 8; ++idx) {
+ if (str[idx] != '~')
+ music._nextSongName += str[idx];
+ else
+ break;
+ }
+ str += 7;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 8;
+ person._npcPath[person._npcIndex + 1] = str[1];
+ person._npcIndex += 2;
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 9;
+ for (int i = 1; i <= 3; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 4;
+ str += 3;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 7;
+ person._npcPath[person._npcIndex + 1] = str[1];
+ person._npcIndex += 2;
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdPassword(const byte *&str) { error("TODO: script opcode (cmdPassword)"); }
+
+OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) {
+ Music &music = *_vm->_music;
+ Common::String currentSong = music._currentSongName;
+
+ // Get the name of the song to play
+ music._currentSongName = "";
+ str++;
+ for (int idx = 0; idx < 8; ++idx) {
+ if (str[idx] != '~')
+ music._currentSongName += str[idx];
+ else
+ break;
+ }
+ str += 7;
+
+ // Play the song
+ music.loadSong(music._currentSongName);
+
+ // Copy the old song name to _nextSongName so that when the new song is finished, the old song will restart
+ music._nextSongName = currentSong;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) {
+ int npcNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ person._misc = 0;
+
+ if (person._seqTo) {
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+ person._sequenceNumber = person._savedNpcSequence;
+ person._frameNumber = person._savedNpcFrame;
+ person.checkWalkGraphics();
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) {
+ int npcNum = *++str;
+ ++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Person &person = people[npcNum];
+
+ // Copy over the NPC examine text until we reach a stop marker, which is
+ // the same as a start marker, or we reach the end of the file
+ while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
+ person._examine += *str++;
+
+ // Move past any leftover text till we reach a stop marker
+ while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF])
+ str++;
+
+ if (!*str)
+ // Reached end of file, so decrement pointer so outer loop will terminate on NULL
+ --str;
+ else
+ // Move past the ending OP_NPC_DEST_ON_OFF opcode
+ ++str;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ person._description = "";
+ int len = *++str;
+ for (int idx = 0; idx < len; ++idx)
+ person._description += str[idx + 1];
+
+ str += len;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int npcNum = *++str;
+ people[npcNum]._type = REMOVE;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int npcNum = *++str;
+ people[npcNum]._type = CHARACTER;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 1;
+ for (int i = 1; i <= 4; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+ person._npcPath[person._npcIndex + 5] = DIRECTION_CONVERSION[str[5] - 1] + 1;
+
+ person._npcIndex += 6;
+ str += 5;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 2;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 5;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 6;
+ for (int i = 1; i <= 2; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 3;
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) {
+ int npcNum = *++str - 1;
+ ++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ int32 posX = (str[0] - 1) * 256 + str[1] - 1;
+ if (posX > 16384)
+ posX = -1 * (posX - 16384);
+ int32 posY = (str[2] - 1) * 256 + str[3] - 1;
+
+ people[npcNum]._position = Point32(posX * FIXED_INT_MULTIPLIER, posY * FIXED_INT_MULTIPLIER);
+ if (person._seqTo && person._walkLoaded) {
+ person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo;
+ person._seqTo = 0;
+ }
+
+ assert(str[4] - 1 < 16);
+ person._sequenceNumber = DIRECTION_CONVERSION[str[4] - 1];
+ person._frameNumber = 0;
+
+ if (person._walkLoaded)
+ person.checkWalkGraphics();
+
+ if (person._walkLoaded && person._type == CHARACTER &&
+ person._sequenceNumber >= STOP_UP && person._sequenceNumber <= STOP_UPLEFT) {
+ bool done = false;
+ do {
+ person.checkSprite();
+ for (int x = 0; x < person._frameNumber; x++) {
+ if (person._walkSequences[person._sequenceNumber]._sequences[x] == 0) {
+ done = true;
+ break;
+ }
+ }
+ } while(!done);
+ }
+
+ str += 4;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) {
+ int npcNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._resetNPCPath) {
+ person._npcIndex = person._npcPause = 0;
+ person._resetNPCPath = false;
+ memset(person._npcPath, 0, 100);
+ }
+
+ person._npcPath[person._npcIndex] = 3;
+ for (int i = 1; i <= 8; i++)
+ person._npcPath[person._npcIndex + i] = str[i];
+
+ person._npcIndex += 9;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Common::String &verb = people[npcNum]._use[verbNum]._verb;
+
+ for (int x = 0; x < 12; x++) {
+ if (str[x + 1] != '~')
+ verb.setChar(str[x + 1], x);
+ else
+ verb.setChar(0, x);
+ }
+
+ verb.setChar(0, 11);
+
+ uint len = verb.size() - 1;
+ while (verb[len] == ' ' && len)
+ len--;
+ verb.setChar(0, len + 1);
+ if (verb != " ")
+ verb.clear();
+ str += 12;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ UseType &useType = people[npcNum]._use[verbNum];
+
+ useType._cAnimNum = (str[1] - 1) & 127;
+ useType._cAnimSpeed = 1 + 128 * (str[1] >= 128);
+ str++;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ UseType &useType = people[npcNum]._use[verbNum];
+ Common::String &name = useType._names[0];
+ name.setChar('*', 0);
+ name.setChar('C', 1);
+
+ for (int x = 0; x < 8; x++) {
+ if (str[x + 1] != '~')
+ name.setChar(str[x + 1], x + 2);
+ else
+ name.setChar(0, x + 2);
+ }
+
+ name.setChar(0, 11);
+ useType._cAnimNum = 99;
+ useType._cAnimSpeed = 1;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) {
+ int npcNum = *++str;
+ int verbNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Common::String &target = people[npcNum]._use[verbNum]._target;
+
+ for (int x = 0; x < 12; x++) {
+ if (str[x + 1] != '~')
+ target.setChar(str[x + 1], x);
+ else
+ target.setChar(0, x);
+ }
+
+ target.setChar(0, 11);
+
+ uint len = target.size() - 1;
+ while (target[len] == ' ' && len)
+ len--;
+ target.setChar(0, len + 1);
+ str += 12;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) {
+ int npcNum = *++str - 1;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Person &person = people[npcNum];
+
+ // Build up walk library name for the given NPC
+ person._walkVGSName = "";
+ for (int idx = 0; idx < 8; ++idx) {
+ if (str[idx + 1] != '~')
+ person._walkVGSName += str[idx + 1];
+ else
+ break;
+ }
+ person._walkVGSName += ".VGS";
+
+ people._forceWalkReload = true;
+ str += 8;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ ++str;
+ int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1);
+
+ int flag1 = flag & 16383;
+ if (flag > 16383)
+ flag1 *= -1;
+
+ str += 2;
+
+ // Make sure that this instance is not already being tracked
+ bool found = false;
+ for (uint idx = 0; idx < scene._sceneTripCounters.size() && !found; ++idx) {
+ SceneTripEntry &entry = scene._sceneTripCounters[idx];
+ if (entry._flag == flag1 && entry._sceneNumber == str[0] - 1)
+ found = true;
+ }
+
+ // Only add it if it's not being tracked already
+ if (!found)
+ scene._sceneTripCounters.push_back(SceneTripEntry(flag1, str[0] - 1, str[1] - 1));
+
+ ++str;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ int speaker = str[1] - 1;
+ int sequenceNumber = str[2];
+
+ if (sequenceNumber < 128)
+ people.setTalkSequence(speaker, sequenceNumber);
+ else
+ people.setListenSequence(speaker, sequenceNumber);
+
+ str += 2;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ ++str;
+ people._walkControl = str[0] - 1;
+
+ return RET_SUCCESS;
+}
+
+// Dummy opcode
+OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsDisable called"); }
+
+// Dummy opcode
+OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); }
+
+OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode (cmdTurnSoundsOff)"); }
+
+OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int cAnimNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ Scene &scene = *_vm->_scene;
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ person.walkToCoords(anim._goto[1], anim._goto[1]._facing);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) {
+ int npcNum = *++str;
+ int cAnimNum = *++str;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+ Scene &scene = *_vm->_scene;
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ person.walkToCoords(anim._goto[1], anim._goto[1]._facing);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) {
+ int npcNum = *++str;
+ str++;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+
+ person.walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
+ DIRECTION_CONVERSION[str[4] - 1]);
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 4;
+ return RET_SUCCESS;
+}
+
+OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) {
+ int npcNum = *++str;
+ str++;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooPerson &person = people[npcNum];
+
+ if (person._pathStack.empty())
+ person.pushNPCPath();
+ person._npcMoved = true;
+
+ int xp = (str[0] - 1) * 256 + str[1] - 1;
+ if (xp > 16384)
+ xp = -1 * (xp - 16384);
+ int yp = (str[2] - 1) * 256 + str[3] - 1;
+
+ person.walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER),
+ DIRECTION_CONVERSION[str[4] - 1]);
+
+ if (_talkToAbort)
+ return RET_EXIT;
+
+ str += 9;
+ return RET_SUCCESS;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_talk.h b/engines/sherlock/tattoo/tattoo_talk.h
new file mode 100644
index 0000000000..8abbb1b8be
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_talk.h
@@ -0,0 +1,101 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_TALK_H
+#define SHERLOCK_TATTOO_TALK_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "common/stream.h"
+#include "common/stack.h"
+#include "sherlock/talk.h"
+#include "sherlock/tattoo/widget_talk.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+class WidgetTalk;
+
+class TattooTalk : public Talk {
+ friend class WidgetTalk;
+private:
+ WidgetTalk _talkWidget;
+
+ OpcodeReturn cmdSwitchSpeaker(const byte *&str);
+ OpcodeReturn cmdMouseOnOff(const byte *&str);
+ OpcodeReturn cmdGotoScene(const byte *&str);
+ OpcodeReturn cmdWalkHolmesToCoords(const byte *&str);
+ OpcodeReturn cmdNextSong(const byte *&str);
+ OpcodeReturn cmdPassword(const byte *&str);
+ OpcodeReturn cmdPlaySong(const byte *&str);
+ OpcodeReturn cmdRestorePeopleSequence(const byte *&str);
+ OpcodeReturn cmdSetNPCDescOnOff(const byte *&str);
+ OpcodeReturn cmdSetNPCInfoLine(const byte *&str);
+ OpcodeReturn cmdNPCLabelGoto(const byte *&str);
+ OpcodeReturn cmdNPCLabelIfFlagGoto(const byte *&str);
+ OpcodeReturn cmdNPCLabelSet(const byte *&str);
+ OpcodeReturn cmdSetNPCOff(const byte *&str);
+ OpcodeReturn cmdSetNPCOn(const byte *&str);
+ OpcodeReturn cmdSetNPCPathDest(const byte *&str);
+ OpcodeReturn cmdSetNPCPathPause(const byte *&str);
+ OpcodeReturn cmdSetNPCPathPauseTakingNotes(const byte *&str);
+ OpcodeReturn cmdSetNPCPathPauseLookingHolmes(const byte *&str);
+ OpcodeReturn cmdSetNPCPosition(const byte *&str);
+ OpcodeReturn cmdSetNPCTalkFile(const byte *&str);
+ OpcodeReturn cmdSetNPCVerb(const byte *&str);
+ OpcodeReturn cmdSetNPCVerbCAnimation(const byte *&str);
+ OpcodeReturn cmdSetNPCVerbScript(const byte *&str);
+ OpcodeReturn cmdSetNPCVerbTarget(const byte *&str);
+ OpcodeReturn cmdSetNPCWalkGraphics(const byte *&str);
+ OpcodeReturn cmdSetSceneEntryFlag(const byte *&str);
+ OpcodeReturn cmdSetTalkSequence(const byte *&str);
+ OpcodeReturn cmdSetWalkControl(const byte *&str);
+ OpcodeReturn cmdTalkInterruptsDisable(const byte *&str);
+ OpcodeReturn cmdTalkInterruptsEnable(const byte *&str);
+ OpcodeReturn cmdTurnSoundsOff(const byte *&str);
+ OpcodeReturn cmdWalkHolmesAndNPCToCAnimation(const byte *&str);
+ OpcodeReturn cmdWalkNPCToCAnimation(const byte *&str);
+ OpcodeReturn cmdWalkNPCToCoords(const byte *&str);
+ OpcodeReturn cmdWalkHomesAndNPCToCoords(const byte *&str);
+protected:
+ /**
+ * Display the talk interface window
+ */
+ virtual void talkInterface(const byte *&str);
+
+ /**
+ * Show the talk display
+ */
+ virtual void showTalk();
+public:
+ TattooTalk(SherlockEngine *vm);
+ virtual ~TattooTalk() {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/tattoo_user_interface.cpp b/engines/sherlock/tattoo/tattoo_user_interface.cpp
new file mode 100644
index 0000000000..ae09ba5fc7
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp
@@ -0,0 +1,862 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo_journal.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm),
+ _inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm), _verbsWidget(vm),
+ _labWidget(vm) {
+ Common::fill(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], 0);
+ Common::fill(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], 0);
+ _scrollSize = 0;
+ _scrollSpeed = 16;
+ _drawMenu = false;
+ _bgShape = nullptr;
+ _personFound = false;
+ _lockoutTimer = 0;
+ _fileMode = SAVEMODE_NONE;
+ _exitZone = -1;
+ _scriptZone = -1;
+ _arrowZone = _oldArrowZone = -1;
+ _activeObj = -1;
+ _cAnimFramePause = 0;
+ _scrollHighlight = SH_NONE;
+ _mask = _mask1 = nullptr;
+ _maskCounter = 0;
+
+ _interfaceImages = new ImageFile("intrface.vgs");
+}
+
+TattooUserInterface::~TattooUserInterface() {
+ delete _interfaceImages;
+}
+
+void TattooUserInterface::initScrollVars() {
+ Screen &screen = *_vm->_screen;
+ _scrollSize = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH;
+ _targetScroll = Common::Point(0, 0);
+ screen._currentScroll = Common::Point(0, 0);
+}
+
+void TattooUserInterface::lookAtObject() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Sound &sound = *_vm->_sound;
+ Talk &talk = *_vm->_talk;
+ Common::Point mousePos = events.mousePos();
+ Common::String desc;
+ int cAnimSpeed = 0;
+
+ _lookPos = mousePos;
+ _menuMode = LOOK_MODE;
+
+ if (_personFound) {
+ desc = people[_bgFound - 1000]._examine;
+ } else {
+ // Check if there is a Look animation
+ if (_bgShape->_lookcAnim != 0) {
+ cAnimSpeed = _bgShape->_lookcAnim & 0xe0;
+ cAnimSpeed >>= 5;
+ ++cAnimSpeed;
+
+ _cAnimFramePause = _bgShape->_lookFrames;
+ desc = _bgShape->_examine;
+
+ int cNum = (_bgShape->_lookcAnim & 0x1f) - 1;
+ scene.startCAnim(cNum);
+ } else if (_bgShape->_lookPosition.y != 0) {
+ // Need to walk to object before looking at it
+ people[HOLMES].walkToCoords(_bgShape->_lookPosition, _bgShape->_lookPosition._facing);
+ }
+
+ if (!talk._talkToAbort) {
+ desc = _bgShape->_examine;
+
+ if (_bgShape->_lookFlag)
+ _vm->setFlags(_bgShape->_lookFlag);
+
+ // Find the Sound File to Play if there is one
+ if (!desc.hasPrefix("_")) {
+ for (uint idx = 0; idx < scene._objSoundList.size(); ++idx) {
+ // Get the object name up to the equals
+ const char *p = strchr(scene._objSoundList[idx].c_str(), '=');
+
+ // Form the name and remove any trailing spaces
+ Common::String name(scene._objSoundList[idx].c_str(), p);
+ while (name.hasSuffix(" "))
+ name.deleteLastChar();
+
+ // See if this Object Sound List entry matches the object's name
+ if (!_bgShape->_name.compareToIgnoreCase(name)) {
+ // Move forward to get the sound filename
+ while ((*p == ' ') || (*p == '='))
+ ++p;
+
+ // If it's not "NONE", play the Sound File
+ Common::String soundName(p);
+ if (soundName.compareToIgnoreCase("NONE")) {
+ soundName.toLowercase();
+ if (!soundName.contains('.'))
+ soundName += ".wav";
+
+ sound.playSound(soundName, WAIT_RETURN_IMMEDIATELY);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Only show the desciption if the object has one, and if no talk file interrupted while walking to it
+ if (!talk._talkToAbort && !desc.empty()) {
+ if (_cAnimFramePause == 0)
+ printObjectDesc(desc, true);
+ else
+ // The description was already printed by an animation
+ _cAnimFramePause = 0;
+ } else if (desc.empty()) {
+ // There was no description to display, so reset back to STD_MODE
+ _menuMode = STD_MODE;
+ }
+}
+
+void TattooUserInterface::printObjectDesc(const Common::String &str, bool firstTime) {
+ Events &events = *_vm->_events;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+
+ if (str.hasPrefix("_")) {
+ // The passed string specifies a talk file
+ _lookScriptFlag = true;
+ events.setCursor(MAGNIFY);
+ int savedSelector = _selector;
+
+ if (!_invLookFlag)
+ _windowOpen = false;
+
+ talk.talkTo(str.c_str() + 1);
+ _lookScriptFlag = false;
+
+ if (talk._talkToAbort) {
+ events.setCursor(ARROW);
+ return;
+ }
+
+ // See if we're looking at an inventory item
+ if (_invLookFlag) {
+ _selector = _oldSelector = savedSelector;
+ doInventory(0);
+ _invLookFlag = false;
+
+ } else {
+ // Nope
+ events.setCursor(ARROW);
+ _key = -1;
+ _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ events._pressed = events._released = events._rightReleased = false;
+ events._oldButtons = 0;
+ }
+ } else {
+ events._pressed = events._released = events._rightReleased = false;
+
+ // Show text dialog
+ _textWidget.load(str);
+ _textWidget.summonWindow();
+
+ if (firstTime)
+ _selector = _oldSelector = -1;
+
+ _drawMenu = _windowOpen = true;
+ }
+}
+
+void TattooUserInterface::doJournal() {
+ TattooJournal &journal = *(TattooJournal *)_vm->_journal;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Screen &screen = *_vm->_screen;
+
+ _menuMode = JOURNAL_MODE;
+ journal.show();
+
+ _menuMode = STD_MODE;
+ _windowOpen = false;
+ _key = -1;
+
+ setupBGArea(screen._cMap);
+ screen.clear();
+ screen.setPalette(screen._cMap);
+
+ screen._backBuffer1.blitFrom(screen._backBuffer2);
+ scene.updateBackground();
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+}
+
+void TattooUserInterface::reset() {
+ UserInterface::reset();
+ _lookPos = Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
+ _tooltipWidget.setText("");
+ _widgets.clear();
+}
+
+void TattooUserInterface::handleInput() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Common::Point mousePos = events.mousePos();
+
+ _vm->_canLoadSave = _menuMode == STD_MODE;
+ events.pollEventsAndWait();
+ _vm->_canLoadSave = false;
+ _keyState.keycode = Common::KEYCODE_INVALID;
+
+ // Check for credits starting
+ if (_vm->readFlags(3000) && !vm._creditsActive)
+ vm.initCredits();
+
+ // Check the mouse positioning
+ if (events.isCursorVisible())
+ _bgFound = scene.findBgShape(mousePos);
+ _personFound = _bgFound >= 1000;
+ _bgShape = (_bgFound != -1 && _bgFound < 1000) ? &scene._bgShapes[_bgFound] : nullptr;
+
+ if (_lockoutTimer)
+ --_lockoutTimer;
+
+ // Key handling
+ if (events.kbHit()) {
+ _keyState = events.getKey();
+
+ if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode)
+ vm._fastMode = !vm._fastMode;
+
+ else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) {
+ // Beging used for testing that Look dialogs work
+ lookAtObject();
+
+ } else if (_keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog && !_lockoutTimer) {
+ vm.setFlags(-76);
+ vm.setFlags(396);
+ scene._goToScene = STARTING_GAME_SCENE;
+ }
+ }
+
+ if (!events.isCursorVisible())
+ _keyState.keycode = Common::KEYCODE_INVALID;
+
+ // If there's any active widgets/windows, let the most recently open one do event processing
+ if (!_widgets.empty())
+ _widgets.back()->handleEvents();
+
+ // Handle input depending on what mode we're in
+ switch (_menuMode) {
+ case STD_MODE:
+ doStandardControl();
+ break;
+ case LOOK_MODE:
+ doLookControl();
+ break;
+ case FILES_MODE:
+ doFileControl();
+ break;
+ default:
+ break;
+ }
+}
+
+void TattooUserInterface::drawInterface(int bufferNum) {
+ Screen &screen = *_vm->_screen;
+ TattooEngine &vm = *(TattooEngine *)_vm;
+
+ // Draw any active on-screen widgets
+ for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
+ (*i)->draw();
+
+ // Handle drawing credits
+ if (vm._creditsActive)
+ vm.drawCredits();
+
+ // Bring the widgets to the screen
+ if (_mask != nullptr)
+ screen._flushScreen = true;
+
+ if (screen._flushScreen)
+ screen.blockMove();
+
+ // Handle drawing the text tooltip if necessary
+ if (_menuMode == STD_MODE || _menuMode == LAB_MODE)
+ _tooltipWidget.draw();
+}
+
+void TattooUserInterface::doBgAnimRestoreUI() {
+ TattooScene &scene = *((TattooScene *)_vm->_scene);
+ Screen &screen = *_vm->_screen;
+
+ // If there are any on-screen widgets, then erase them
+ for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
+ (*i)->erase();
+
+ // If there is a Text Tag being display, restore the area underneath it
+ _tooltipWidget.erase();
+
+ // If a canimation is active, restore the graphics underneath it
+ if (scene._activeCAnim.active())
+ screen.restoreBackground(scene._activeCAnim._oldBounds);
+
+ // If a canimation just ended, remove it's graphics from the backbuffer
+ if (scene._activeCAnim._removeBounds.width() > 0)
+ screen.restoreBackground(scene._activeCAnim._removeBounds);
+}
+
+void TattooUserInterface::doScroll() {
+ Screen &screen = *_vm->_screen;
+
+ // If we're already at the target scroll position, nothing needs to be done
+ if (_targetScroll.x == screen._currentScroll.x)
+ return;
+
+ screen._flushScreen = true;
+ if (_targetScroll.x > screen._currentScroll.x) {
+ screen._currentScroll.x += _scrollSpeed;
+ if (screen._currentScroll.x > _targetScroll.x)
+ screen._currentScroll.x = _targetScroll.x;
+ } else if (_targetScroll.x < screen._currentScroll.x) {
+ screen._currentScroll.x -= _scrollSpeed;
+ if (screen._currentScroll.x < _targetScroll.x)
+ screen._currentScroll.x = _targetScroll.x;
+ }
+}
+
+void TattooUserInterface::doStandardControl() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+ Common::Point mousePos = events.mousePos();
+ bool noDesc = false;
+
+ // Don't do any input processing whilst the prolog is running
+ if (vm._runningProlog)
+ return;
+
+ // Display the names of any Objects the cursor is pointing at
+ displayObjectNames();
+
+ switch (_keyState.keycode) {
+ case Common::KEYCODE_F5:
+ // Save game
+ freeMenu();
+ _fileMode = SAVEMODE_SAVE;
+ initFileMenu();
+ return;
+
+ case Common::KEYCODE_F7:
+ // Load game
+ freeMenu();
+ _fileMode = SAVEMODE_LOAD;
+ initFileMenu();
+ return;
+
+ case Common::KEYCODE_F1:
+ // Display journal
+ if (vm.readFlags(FLAG_PLAYER_IS_HOLMES)) {
+ freeMenu();
+ doJournal();
+
+ // See if we're in a Lab Table Room
+ _menuMode = (scene._labTableScene) ? LAB_MODE : STD_MODE;
+ return;
+ }
+ break;
+
+ case Common::KEYCODE_TAB:
+ case Common::KEYCODE_F3:
+ // Display inventory
+ freeMenu();
+ doInventory(3);
+ return;
+
+ case Common::KEYCODE_F4:
+ // Display options
+ freeMenu();
+ doControls();
+ return;
+
+ case Common::KEYCODE_F10:
+ // Quit menu
+ freeMenu();
+ doQuitMenu();
+ return;
+
+ default:
+ break;
+ }
+
+ // See if a mouse button was released
+ if (events._released || events._rightReleased) {
+ // See if the mouse was released in an exit (Arrow) zone. Unless it's also pointing at an object
+ // within the zone, in which case the object gets precedence
+ _exitZone = -1;
+ if (_arrowZone != -1 && events._released)
+ _exitZone = _arrowZone;
+
+ // Turn any Text display off
+ if (_arrowZone == -1 || events._rightReleased)
+ freeMenu();
+
+ if (_personFound) {
+ if (people[_bgFound - 1000]._description.empty() || people[_bgFound - 1000]._description.hasPrefix(" "))
+ noDesc = true;
+ } else if (_bgFound != -1) {
+ if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" "))
+ noDesc = true;
+ } else {
+ noDesc = true;
+ }
+
+ if (events._rightReleased) {
+ // Show the verbs menu for the highlighted object
+ _tooltipWidget.banishWindow();
+ _verbsWidget.load(!noDesc);
+ _verbsWidget.summonWindow();
+
+ _selector = _oldSelector = -1;
+ _activeObj = _bgFound;
+ _menuMode = VERB_MODE;
+ } else if (_personFound || (_bgFound != -1 && _bgFound < 1000 && _bgShape->_aType == PERSON)) {
+ // The object found is a person (the default for people is TALK)
+ talk.talk(_bgFound);
+ _activeObj = -1;
+ } else if (!noDesc) {
+ // Either call the code to Look at it's Examine Field or call the Exit animation
+ // if the object is an exit, specified by the first four characters of the name being "EXIT"
+ Common::String name = _personFound ? people[_bgFound - 1000]._name : _bgShape->_name;
+ if (!name.hasPrefix("EXIT")) {
+ lookAtObject();
+ } else {
+ // Run the Exit animation and set which scene to go to next
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_bgShape->_use[idx]._verb.compareToIgnoreCase("Open")) {
+ checkAction(_bgShape->_use[idx], _bgFound);
+ _activeObj = -1;
+ }
+ }
+ }
+ } else {
+ // See if there are any Script Zones where they clicked
+ if (scene.checkForZones(mousePos, _scriptZone) != 0) {
+ // Mouse click in a script zone
+ events._pressed = events._released = false;
+ } else if (scene.checkForZones(mousePos, NOWALK_ZONE) != 0) {
+ events._pressed = events._released = false;
+ } else {
+ // Walk to where the mouse was clicked
+ people[HOLMES]._walkDest = mousePos;
+ people[HOLMES].goAllTheWay();
+ }
+ }
+ }
+}
+
+void TattooUserInterface::doLookControl() {
+ Events &events = *_vm->_events;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Sound &sound = *_vm->_sound;
+
+ // See if a mouse button was released or a key pressed, and we want to initiate an action
+ // TODO: Not sure about _soundOn.. should be check for speaking voice for text being complete
+ if (events._released || events._rightReleased || _keyState.keycode || (sound._voices && !sound._soundOn)) {
+ // See if we were looking at an inventory object
+ if (!_invLookFlag) {
+ // See if there is any more text to display
+ if (!_textWidget._remainingText.empty()) {
+ printObjectDesc(_textWidget._remainingText, false);
+ } else {
+ // Otherwise restore the background and go back into STD_MODE
+ freeMenu();
+ _key = -1;
+ _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+
+ events.setCursor(ARROW);
+ events._pressed = events._released = events._rightReleased = false;
+ events._oldButtons = 0;
+ }
+ } else {
+ // We were looking at a Inventory object
+ // Erase the text window, and then redraw the inventory window
+ _textWidget.banishWindow();
+ doInventory(0);
+
+ _invLookFlag = false;
+ _key = -1;
+
+ events.setCursor(ARROW);
+ events._pressed = events._released = events._rightReleased = false;
+ events._oldButtons = 0;
+ }
+ }
+}
+
+void TattooUserInterface::doFileControl() {
+ warning("TODO: ui control (file)");
+}
+
+void TattooUserInterface::displayObjectNames() {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ Common::Point mousePos = events.mousePos();
+ _arrowZone = -1;
+
+ if (_bgFound == -1 || scene._currentScene == 90) {
+ for (uint idx = 0; idx < scene._exits.size() && _arrowZone == -1; ++idx) {
+ Exit &exit = scene._exits[idx];
+ if (exit.contains(mousePos))
+ _arrowZone = idx;
+ }
+ }
+
+ _tooltipWidget.handleEvents();
+ _oldArrowZone = _arrowZone;
+}
+
+void TattooUserInterface::initFileMenu() {
+ // TODO
+}
+
+void TattooUserInterface::doInventory(int mode) {
+ People &people = *_vm->_people;
+ people[HOLMES].gotoStand();
+
+ _inventoryWidget.load(mode);
+ _inventoryWidget.summonWindow();
+
+ _menuMode = INV_MODE;
+}
+
+void TattooUserInterface::doControls() {
+ // TODO
+}
+
+void TattooUserInterface::pickUpObject(int objNum) {
+ // TOOD
+}
+
+void TattooUserInterface::doQuitMenu() {
+ // TODO
+}
+
+void TattooUserInterface::putMessage(const char *formatStr, ...) {
+ // Create the string to display
+ va_list args;
+ va_start(args, formatStr);
+ Common::String str = Common::String::vformat(formatStr, args);
+ va_end(args);
+
+ // Open the message widget
+ _menuMode = MESSAGE_MODE;
+ _messageWidget.load(str, 25);
+ _messageWidget.summonWindow();
+}
+
+void TattooUserInterface::setupBGArea(const byte cMap[PALETTE_SIZE]) {
+ Scene &scene = *_vm->_scene;
+
+ // This requires that there is a 16 grayscale palette sequence in the palette that goes from lighter
+ // to darker as the palette numbers go up. The last palette entry in that run is specified by _bgColor
+ byte *p = &_lookupTable[0];
+ for (int idx = 0; idx < PALETTE_COUNT; ++idx)
+ *p++ = BG_GREYSCALE_RANGE_END - ((cMap[idx * 3] / 4) * 30 + (cMap[idx * 3 + 1] / 4) * 59 +
+ (cMap[idx * 3 + 2] / 4) * 11) / 480;
+
+ // If we're going to a scene with a haze special effect, initialize the translate table to lighten the colors
+ if (_mask != nullptr) {
+ p = &_lookupTable1[0];
+
+ for (int idx = 0; idx < PALETTE_COUNT; ++idx) {
+ int r, g, b;
+ switch (scene._currentScene) {
+ case 8:
+ r = cMap[idx * 3] * 4 / 5;
+ g = cMap[idx * 3 + 1] * 3 / 4;
+ b = cMap[idx * 3 + 2] * 3 / 4;
+ break;
+
+ case 18:
+ case 68:
+ r = cMap[idx * 3] * 4 / 3;
+ g = cMap[idx * 3 + 1] * 4 / 3;
+ b = cMap[idx * 3 + 2] * 4 / 3;
+ break;
+
+ case 7:
+ case 53:
+ r = cMap[idx * 3] * 4 / 3;
+ g = cMap[idx * 3 + 1] * 4 / 3;
+ b = cMap[idx * 3 + 2] * 4 / 3;
+ break;
+
+ default:
+ r = g = b = 0;
+ break;
+ }
+
+ byte c = 0;
+ int cd = (r - cMap[0]) * (r - cMap[0]) + (g - cMap[1]) * (g - cMap[1]) + (b - cMap[2]) * (b - cMap[2]);
+
+ for (int pal = 0; pal < PALETTE_COUNT; ++pal) {
+ int d = (r - cMap[pal * 3]) * (r - cMap[pal * 3]) + (g - cMap[pal * 3 + 1]) * (g - cMap[pal * 3 + 1])
+ + (b - cMap[pal * 3 + 2])*(b - cMap[pal * 3 + 2]);
+
+ if (d < cd) {
+ c = pal;
+ cd = d;
+ if (!d)
+ break;
+ }
+ }
+ *p++ = c;
+ }
+ }
+}
+
+void TattooUserInterface::doBgAnimEraseBackground() {
+ TattooEngine &vm = *((TattooEngine *)_vm);
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+
+ static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 };
+
+ if (_mask != nullptr) {
+ screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT);
+
+ switch (scene._currentScene) {
+ case 7:
+ if (++_maskCounter == 2) {
+ _maskCounter = 0;
+ if (--_maskOffset.x < 0)
+ _maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1;
+ }
+ break;
+
+ case 8:
+ _maskOffset.x += 2;
+ if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH)
+ _maskOffset.x = 0;
+ break;
+
+ case 18:
+ case 68:
+ ++_maskCounter;
+ if (_maskCounter / 4 >= 16)
+ _maskCounter = 0;
+
+ _maskOffset.x = OFFSETS[_maskCounter / 4];
+ break;
+
+ case 53:
+ if (++_maskCounter == 2) {
+ _maskCounter = 0;
+ if (++_maskOffset.x == screen._backBuffer1.w())
+ _maskOffset.x = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ // Standard scene without mask, so call user interface to erase any UI elements as necessary
+ doBgAnimRestoreUI();
+
+ // Restore background for any areas covered by characters and shapes
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
+ screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y,
+ people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y));
+
+ for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
+ Object &obj = scene._bgShapes[idx];
+
+ if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) ||
+ obj._type == HIDE_SHAPE || obj._type == REMOVE)
+ screen._backBuffer1.blitFrom(screen._backBuffer2, obj._oldPosition,
+ Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x,
+ obj._oldPosition.y + obj._oldSize.y));
+ }
+
+ // If credits are active, erase the area they cover
+ if (vm._creditsActive)
+ vm.eraseCredits();
+ }
+
+ for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
+ Object &obj = scene._bgShapes[idx];
+
+ if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) {
+ screen._backBuffer1.blitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds());
+
+ obj._oldPosition = obj._position;
+ obj._oldSize = obj._noShapeSize;
+ }
+ }
+
+ // Adjust the Target Scroll if needed
+ if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) <
+ (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) {
+
+ _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
+ SHERLOCK_SCREEN_WIDTH / 8 - 250);
+ if (_targetScroll.x < 0)
+ _targetScroll.x = 0;
+ }
+
+ if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) >
+ (SHERLOCK_SCREEN_WIDTH / 4 * 3) && people[people._walkControl]._delta.x > 0)
+ _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER -
+ SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250);
+
+ if (_targetScroll.x > _scrollSize)
+ _targetScroll.x = _scrollSize;
+
+ doScroll();
+}
+
+void TattooUserInterface::drawMaskArea(bool mode) {
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ int xp = mode ? _maskOffset.x : 0;
+
+ if (_mask != nullptr) {
+ switch (scene._currentScene) {
+ case 7:
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110));
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110));
+ break;
+
+ case 8:
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180));
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180));
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180));
+ if (!_vm->readFlags(880))
+ screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300));
+ break;
+
+ case 18:
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(xp, 203));
+ if (!_vm->readFlags(189))
+ screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + xp, 239));
+ break;
+
+ case 53:
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110));
+ if (mode)
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110));
+ break;
+
+ case 68:
+ screen._backBuffer1.maskArea((*_mask)[0], Common::Point(xp, 203));
+ screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + xp, 239));
+ break;
+ }
+ }
+}
+
+void TattooUserInterface::makeBGArea(const Common::Rect &r) {
+ Screen &screen = *_vm->_screen;
+
+ for (int yp = r.top; yp < r.bottom; ++yp) {
+ byte *ptr = screen._backBuffer1.getBasePtr(r.left, yp);
+
+ for (int xp = r.left; xp < r.right; ++xp, ++ptr)
+ *ptr = _lookupTable[*ptr];
+ }
+
+ screen.slamRect(r);
+}
+
+void TattooUserInterface::drawDialogRect(Surface &s, const Common::Rect &r, bool raised) {
+ if (raised) {
+ // Draw Left
+ s.vLine(r.left, r.top, r.bottom - 1, INFO_TOP);
+ s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_TOP);
+ // Draw Top
+ s.hLine(r.left + 2, r.top, r.right - 1, INFO_TOP);
+ s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_TOP);
+ // Draw Right
+ s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_BOTTOM);
+ s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_BOTTOM);
+ // Draw Bottom
+ s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_BOTTOM);
+ s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_BOTTOM);
+
+ } else {
+ // Draw Left
+ s.vLine(r.left, r.top, r.bottom - 1, INFO_BOTTOM);
+ s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_BOTTOM);
+ // Draw Top
+ s.hLine(r.left + 2, r.top, r.right - 1, INFO_BOTTOM);
+ s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_BOTTOM);
+ // Draw Right
+ s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_TOP);
+ s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_TOP);
+ // Draw Bottom
+ s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_TOP);
+ s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_TOP);
+ }
+}
+
+void TattooUserInterface::banishWindow(bool slideUp) {
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ if (!_widgets.empty())
+ _widgets.back()->banishWindow();
+
+ if (scene._labTableScene && !_labWidget.active()) {
+ // In the lab table scene, so ensure
+ _labWidget.summonWindow();
+ _menuMode = LAB_MODE;
+ }
+}
+
+void TattooUserInterface::freeMenu() {
+ for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i)
+ (*i)->erase();
+ _widgets.clear();
+}
+
+void TattooUserInterface::clearWindow() {
+ banishWindow();
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/tattoo_user_interface.h b/engines/sherlock/tattoo/tattoo_user_interface.h
new file mode 100644
index 0000000000..1cefec688d
--- /dev/null
+++ b/engines/sherlock/tattoo/tattoo_user_interface.h
@@ -0,0 +1,229 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_UI_H
+#define SHERLOCK_TATTOO_UI_H
+
+#include "common/scummsys.h"
+#include "common/list.h"
+#include "sherlock/saveload.h"
+#include "sherlock/screen.h"
+#include "sherlock/user_interface.h"
+#include "sherlock/tattoo/widget_inventory.h"
+#include "sherlock/tattoo/widget_lab.h"
+#include "sherlock/tattoo/widget_text.h"
+#include "sherlock/tattoo/widget_tooltip.h"
+#include "sherlock/tattoo/widget_verbs.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define BUTTON_SIZE 15 // Button width/height
+
+class WidgetBase;
+
+enum ScrollHighlight { SH_NONE = 0, SH_SCROLL_UP = 1, SH_PAGE_UP = 2, SH_THUMBNAIL = 3, SH_PAGE_DOWN = 4, SH_SCROLL_DOWN = 5 };
+
+class TattooUserInterface : public UserInterface {
+ friend class WidgetBase;
+private:
+ int _lockoutTimer;
+ SaveMode _fileMode;
+ int _scriptZone;
+ int _cAnimFramePause;
+ WidgetInventory _inventoryWidget;
+ WidgetMessage _messageWidget;
+ Common::List<WidgetBase *> _widgets;
+ byte _lookupTable[PALETTE_COUNT];
+ byte _lookupTable1[PALETTE_COUNT];
+private:
+ /**
+ * Handle any input when we're in standard mode (with no windows open)
+ */
+ void doStandardControl();
+
+ /**
+ * Handle input when in look mode
+ */
+ void doLookControl();
+
+ /**
+ * Handle input when the File window is open
+ */
+ void doFileControl();
+
+ /**
+ * Handle input while the verb menu is open
+ */
+ void doVerbControl();
+
+ /**
+ * Set up to display the Files menu
+ */
+ void initFileMenu();
+
+ /**
+ * Handle displaying the quit menu
+ */
+ void doQuitMenu();
+
+ /**
+ * Free any active menu
+ */
+ void freeMenu();
+public:
+ Common::Point _targetScroll;
+ int _scrollSize, _scrollSpeed;
+ bool _drawMenu;
+ int _arrowZone, _oldArrowZone;
+ Object *_bgShape;
+ bool _personFound;
+ int _activeObj;
+ Common::KeyState _keyState;
+ Common::Point _lookPos;
+ ScrollHighlight _scrollHighlight;
+ ImageFile *_mask, *_mask1;
+ Common::Point _maskOffset;
+ int _maskCounter;
+ ImageFile *_interfaceImages;
+ WidgetText _textWidget;
+ WidgetLab _labWidget;
+ WidgetVerbs _verbsWidget;
+ WidgetSceneTooltip _tooltipWidget;
+public:
+ TattooUserInterface(SherlockEngine *vm);
+ virtual ~TattooUserInterface();
+
+ /**
+ * Handles restoring any areas of the back buffer that were/are covered by UI elements
+ */
+ void doBgAnimRestoreUI();
+
+ /**
+ * Checks to see if the screen needs to be scrolled. If so, scrolls it towards the target position
+ */
+ void doScroll();
+
+ /**
+ * Initializes scroll variables
+ */
+ void initScrollVars();
+
+ /**
+ * Display the long description for an object in a window
+ */
+ void lookAtObject();
+
+ /**
+ * Display the passed long description for an object. If the flag firstTime is set,
+ * the window will be opened to accomodate the text. Otherwise, the remaining text
+ * will be printed in an already open window
+ */
+ void printObjectDesc(const Common::String &str, bool firstTime);
+
+ /**
+ * Handles displaying the journal
+ */
+ void doJournal();
+
+ /**
+ * Put the game in inventory mode by opening the inventory dialog
+ */
+ void doInventory(int mode);
+
+ /**
+ * Handle the display of the options/setup menu
+ */
+ void doControls();
+
+ /**
+ * Pick up the selected object
+ */
+ void pickUpObject(int objNum);
+
+ /**
+ * This will display a text message in a dialog at the bottom of the screen
+ */
+ void putMessage(const char *formatStr, ...) GCC_PRINTF(2, 3);
+
+ /**
+ * Makes a greyscale translation table for each palette entry in the table
+ */
+ void setupBGArea(const byte cMap[PALETTE_SIZE]);
+
+ /**
+ * Erase any background as needed before drawing frame
+ */
+ void doBgAnimEraseBackground();
+
+ void drawMaskArea(bool mode);
+
+ /**
+ * Translate a given area of the back buffer to greyscale shading
+ */
+ void makeBGArea(const Common::Rect &r);
+
+ /**
+ * Draws all the dialog rectangles for any items that need them
+ */
+ void drawDialogRect(Surface &s, const Common::Rect &r, bool raised);
+
+ /**
+ * If the mouse cursor is point at the cursor, then display the name of the object on the screen.
+ * If there is no object being pointed it, clear any previously displayed name
+ */
+ void displayObjectNames();
+public:
+ /**
+ * Resets the user interface
+ */
+ virtual void reset();
+
+ /**
+ * Main input handler for the user interface
+ */
+ virtual void handleInput();
+
+ /**
+ * Draw the user interface onto the screen's back buffers
+ */
+ virtual void drawInterface(int bufferNum = 3);
+
+ /**
+ * Clear any active text window
+ */
+ virtual void clearWindow();
+
+ /**
+ * Banish any active window
+ * @remarks Tattoo doesn't use sliding windows, but the parameter is in the base
+ * UserInterface class as a convenience for Scalpel UI code
+ */
+ virtual void banishWindow(bool slideUp = true);
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp
new file mode 100644
index 0000000000..9d95fed9bf
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_base.cpp
@@ -0,0 +1,299 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_base.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_talk.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+WidgetBase::WidgetBase(SherlockEngine *vm) : _vm(vm) {
+ _scroll = false;
+}
+
+void WidgetBase::summonWindow() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ // Double-check that the same widget isn't added twice
+ for (Common::List<WidgetBase *>::iterator i = ui._widgets.begin(); i != ui._widgets.end(); ++i) {
+ if ((*i) == this)
+ error("Tried to add a widget twice");
+ }
+
+ // Add widget to the screen
+ ui._widgets.push_back(this);
+ _outsideMenu = false;
+
+ draw();
+}
+
+void WidgetBase::banishWindow() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ erase();
+ _surface.free();
+ ui._widgets.remove(this);
+}
+
+bool WidgetBase::active() const {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ for (Common::List<WidgetBase *>::iterator i = ui._widgets.begin(); i != ui._widgets.end(); ++i) {
+ if ((*i) == this)
+ return true;
+ }
+
+ return false;
+}
+
+
+void WidgetBase::erase() {
+ Screen &screen = *_vm->_screen;
+
+ if (_oldBounds.width() > 0) {
+ // Restore the affected area from the secondary back buffer into the first one, and then copy to screen
+ screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds);
+ screen.slamRect(_oldBounds);
+
+ // Reset the old bounds so it won't be erased again
+ _oldBounds = Common::Rect(0, 0, 0, 0);
+ }
+}
+
+void WidgetBase::draw() {
+ Screen &screen = *_vm->_screen;
+
+ // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it
+ if (_oldBounds.width() > 0 && _oldBounds != _bounds)
+ erase();
+
+ if (_bounds.width() > 0 && !_surface.empty()) {
+ // Get the area to draw, adjusted for scroll position
+ restrictToScreen();
+
+ // Draw the background for the widget
+ drawBackground();
+
+ // Draw the widget onto the back buffer and then slam it to the screen
+ screen._backBuffer1.transBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top));
+ screen.slamRect(_bounds);
+
+ // Store a copy of the drawn area for later erasing
+ _oldBounds = _bounds;
+ }
+}
+
+void WidgetBase::drawBackground() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Screen &screen = *_vm->_screen;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ Common::Rect bounds = _bounds;
+
+ if (vm._transparentMenus) {
+ ui.makeBGArea(bounds);
+ } else {
+ bounds.grow(-3);
+ screen._backBuffer1.fillRect(bounds, MENU_BACKGROUND);
+ }
+}
+
+Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) {
+ Talk &talk = *_vm->_talk;
+ const char *strP = str.c_str();
+
+ // Loop counting up lines
+ lines.clear();
+ do {
+ int width = 0;
+ const char *spaceP = nullptr;
+ const char *lineStartP = strP;
+
+ // Find how many characters will fit on the next line
+ while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] ||
+ (byte)*strP == talk._opcodes[OP_NULL])) {
+ width += _surface.charWidth(*strP);
+
+ // Keep track of the last space
+ if (*strP == ' ')
+ spaceP = strP;
+ ++strP;
+ }
+
+ // If the line was too wide to fit on a single line, go back to the last space
+ // if there was one, or otherwise simply break the line at this point
+ if (width >= maxWidth && spaceP != nullptr)
+ strP = spaceP;
+
+ // Add the line to the output array
+ lines.push_back(Common::String(lineStartP, strP));
+
+ // Move the string ahead to the next line
+ if (*strP == ' ' || *strP == 13)
+ ++strP;
+ } while (*strP && (lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER]
+ || (byte)*strP == talk._opcodes[OP_NULL]));
+
+ // Return any remaining text left over
+ return *strP ? Common::String(strP) : Common::String();
+}
+
+void WidgetBase::restrictToScreen() {
+ Screen &screen = *_vm->_screen;
+
+ if (_bounds.left < screen._currentScroll.x)
+ _bounds.moveTo(screen._currentScroll.x, _bounds.top);
+ if (_bounds.top < 0)
+ _bounds.moveTo(_bounds.left, 0);
+ if (_bounds.right > screen._backBuffer1.w())
+ _bounds.moveTo(screen._backBuffer1.w() - _bounds.width(), _bounds.top);
+ if (_bounds.bottom > screen._backBuffer1.h())
+ _bounds.moveTo(_bounds.left, screen._backBuffer1.h() - _bounds.height());
+}
+
+void WidgetBase::makeInfoArea(Surface &s) {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ImageFile &images = *ui._interfaceImages;
+
+ // Draw the four corners of the Info Box
+ s.transBlitFrom(images[0], Common::Point(0, 0));
+ s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0));
+ s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height));
+ s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h()));
+
+ // Draw the top of the Info Box
+ s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP);
+ s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE);
+ s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM);
+
+ // Draw the bottom of the Info Box
+ s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP);
+ s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE);
+ s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM);
+
+ // Draw the left Side of the Info Box
+ s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP);
+ s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE);
+ s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM);
+
+ // Draw the right Side of the Info Box
+ s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP);
+ s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE);
+ s.vLine(s.w() - 1, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM);
+}
+
+void WidgetBase::makeInfoArea() {
+ makeInfoArea(_surface);
+}
+
+void WidgetBase::checkTabbingKeys(int numOptions) {
+}
+
+void WidgetBase::drawScrollBar(int index, int pageSize, int count) {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ bool raised;
+
+ // Fill the area with transparency
+ Common::Rect r(BUTTON_SIZE, _bounds.height() - 6);
+ r.moveTo(_bounds.width() - BUTTON_SIZE - 3, 3);
+ _surface.fillRect(r, TRANSPARENCY);
+
+ raised = ui._scrollHighlight != 1;
+ _surface.fillRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.top + BUTTON_SIZE - 2), INFO_MIDDLE);
+ ui.drawDialogRect(_surface, Common::Rect(r.left, r.top, r.left + BUTTON_SIZE, r.top + BUTTON_SIZE), raised);
+
+ raised = ui._scrollHighlight != 5;
+ _surface.fillRect(Common::Rect(r.left + 2, r.bottom - BUTTON_SIZE + 2, r.right - 2, r.bottom - 2), INFO_MIDDLE);
+ ui.drawDialogRect(_surface, Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom), raised);
+
+ // Draw the arrows on the scroll buttons
+ byte color = index ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ _surface.hLine(r.right / 2, r.top - 2 + BUTTON_SIZE / 2, r.right / 2, color);
+ _surface.fillRect(Common::Rect(r.right / 2 - 1, r.top - 1 + BUTTON_SIZE / 2,
+ r.right / 2 + 1, r.top - 1 + BUTTON_SIZE / 2), color);
+ _surface.fillRect(Common::Rect(r.right / 2 - 2, r.top + BUTTON_SIZE / 2,
+ r.right / 2 + 2, r.top + BUTTON_SIZE / 2), color);
+ _surface.fillRect(Common::Rect(r.right / 2 - 3, r.top + 1 + BUTTON_SIZE / 2,
+ r.right / 2 + 3, r.top + 1 + BUTTON_SIZE / 2), color);
+
+ color = (index + pageSize) < count ? INFO_BOTTOM + 2 : INFO_BOTTOM;
+ _surface.fillRect(Common::Rect(r.right / 2 - 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2,
+ r.right / 2 + 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2), color);
+ _surface.fillRect(Common::Rect(r.right / 2 - 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2,
+ r.right / 2 + 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2), color);
+ _surface.fillRect(Common::Rect(r.right / 2 - 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2,
+ r.right / 2 + 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2), color);
+ _surface.fillRect(Common::Rect(r.right / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2,
+ r.right / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2), color);
+
+ // Draw the scroll position bar
+ int barHeight = (_bounds.height() - BUTTON_SIZE * 2) * pageSize / count;
+ barHeight = CLIP(barHeight, BUTTON_SIZE, _bounds.height() - BUTTON_SIZE * 2);
+ int barY = (r.height() - BUTTON_SIZE * 2 - barHeight) * index / pageSize + r.top + BUTTON_SIZE;
+
+ _surface.fillRect(Common::Rect(r.left + 2, barY + 2, r.right - 2, barY + barHeight - 3), INFO_MIDDLE);
+ ui.drawDialogRect(_surface, Common::Rect(r.left, barY, r.right, barY + barHeight), true);
+}
+
+void WidgetBase::handleScrollbarEvents(int index, int pageSize, int count) {
+ Events &events = *_vm->_events;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+
+ // If they have selected the sollbar, return with the Scroll Bar Still selected
+ if (ui._scrollHighlight == 3)
+ return;
+
+ ui._scrollHighlight = SH_NONE;
+
+ if ((!events._pressed && !events._rightReleased) || !_scroll)
+ return;
+
+ Common::Rect r(_bounds.right - BUTTON_SIZE - 3, _bounds.top + 3, _bounds.right - 3, _bounds.bottom - 3);
+
+ // Calculate the Scroll Position bar
+ int barHeight = (_bounds.height() - BUTTON_SIZE * 2) * pageSize / count;
+ barHeight = CLIP(barHeight, BUTTON_SIZE, _bounds.height() - BUTTON_SIZE * 2);
+ int barY = (r.height() - BUTTON_SIZE * 2 - barHeight) * index / pageSize + r.top + BUTTON_SIZE;
+
+ if (Common::Rect(r.left, r.top, r.right, r.top + BUTTON_SIZE).contains(mousePos))
+ // Mouse on scroll up button
+ ui._scrollHighlight = SH_SCROLL_UP;
+ else if (Common::Rect(r.left, r.top + BUTTON_SIZE, r.right, barY).contains(mousePos))
+ // Mouse on paging up area (the area of the vertical bar above the thumbnail)
+ ui._scrollHighlight = SH_PAGE_UP;
+ else if (Common::Rect(r.left, barY, r.right, barY + barHeight).contains(mousePos))
+ // Mouse on scrollbar thumb
+ ui._scrollHighlight = SH_THUMBNAIL;
+ else if (Common::Rect(r.left, barY + barHeight, r.right, r.bottom - BUTTON_SIZE).contains(mousePos))
+ // Mouse on paging down area (the area of the vertical bar below the thumbnail)
+ ui._scrollHighlight = SH_PAGE_DOWN;
+ else if (Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom).contains(mousePos))
+ // Mouse on scroll down button
+ ui._scrollHighlight = SH_SCROLL_DOWN;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_base.h b/engines/sherlock/tattoo/widget_base.h
new file mode 100644
index 0000000000..fcf22cee8e
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_base.h
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_BASE_H
+#define SHERLOCK_TATTOO_WIDGET_BASE_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "sherlock/surface.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+class ImageFile;
+
+namespace Tattoo {
+
+class WidgetBase {
+protected:
+ SherlockEngine *_vm;
+ Common::Rect _bounds;
+ Common::Rect _oldBounds;
+ Surface _surface;
+ bool _outsideMenu;
+ bool _scroll;
+
+ /**
+ * Used by descendent classes to split up long text for display across multiple lines
+ */
+ Common::String splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines);
+
+ /**
+ * Ensure that menu is drawn entirely on-screen
+ */
+ void restrictToScreen();
+
+ /**
+ * Draw a window frame around the dges of the passed surface
+ */
+ void makeInfoArea(Surface &s);
+
+ /**
+ * Draw a window frame around the widget's surface
+ */
+ void makeInfoArea();
+
+ /**
+ * Draw the scrollbar for the dialog
+ */
+ void drawScrollBar(int index, int pageSize, int count);
+
+ /**
+ * Handles any events when the mouse is on the scrollbar
+ */
+ void handleScrollbarEvents(int index, int pageSize, int count);
+
+ /**
+ * Handle drawing the background on the area the widget is going to cover
+ */
+ virtual void drawBackground();
+public:
+ WidgetBase(SherlockEngine *vm);
+ virtual ~WidgetBase() {}
+
+ /**
+ * Returns true if the given widget is active in the user interface's widget list
+ */
+ bool active() const;
+
+ /**
+ * Erase any previous display of the widget on the screen
+ */
+ virtual void erase();
+
+ /**
+ * Update the display of the widget on the screen
+ */
+ virtual void draw();
+
+ /**
+ * Used by some descendents to check for keys to mouse the mouse within the dialog
+ */
+ void checkTabbingKeys(int numOptions);
+
+ /**
+ * Summon the window
+ */
+ virtual void summonWindow();
+
+ /**
+ * Close a currently active menu
+ */
+ virtual void banishWindow();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents() {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp
new file mode 100644
index 0000000000..170fb02481
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_inventory.cpp
@@ -0,0 +1,762 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_inventory.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define INVENTORY_XSIZE 70 // Width of the box that surrounds inventory items
+#define INVENTORY_YSIZE 70 // Height of the box that surrounds inventory items
+#define MAX_INV_COMMANDS 10 // Maximum elements in dialog
+
+WidgetInventoryTooltip::WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner) :
+ WidgetTooltipBase(vm), _owner(owner) {
+}
+
+void WidgetInventoryTooltip::setText(const Common::String &str) {
+ // If no text specified, erase any previously displayed tooltip and free it's surface
+ if (str.empty()) {
+ erase();
+ _surface.free();
+ return;
+ }
+
+ int width = _surface.stringWidth(str) + 2;
+ int height = 0;
+ Common::String line1 = str, line2;
+
+ // See if we need to split it into two lines
+ if (width > 150) {
+ // Yes, we do
+ const char *s = str.c_str();
+ const char *space = nullptr;
+ int dif = 10000;
+
+ while (*s) {
+ s = strchr(s, ' ');
+
+ if (!s) {
+ if (!space) {
+ height = _surface.stringHeight(str) + 2;
+ } else {
+ line1 = Common::String(str.c_str(), space);
+ line2 = Common::String(space + 1);
+ height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4;
+ }
+ break;
+ } else {
+ line1 = Common::String(str.c_str(), s);
+ line2 = Common::String(s + 1);
+ int width1 = _surface.stringWidth(line1);
+ int width2 = _surface.stringWidth(line2);
+
+ if (ABS(width1 - width2) < dif) {
+ // Found a split point that results in less overall width
+ space = s;
+ dif = ABS(width1 - width2);
+ width = MAX(width1, width2);
+ }
+
+ s++;
+ }
+ }
+ } else {
+ height = _surface.stringHeight(str) + 2;
+ }
+
+ // Allocate a fresh surface for the new string
+ _bounds = Common::Rect(width, height);
+ _surface.create(width, height);
+ _surface.fill(TRANSPARENCY);
+
+ if (line2.empty()) {
+ _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP);
+ } else {
+ int xp, yp;
+
+ xp = (_bounds.width() - _surface.stringWidth(line1) - 2) / 2;
+ _surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP);
+
+ xp = (_bounds.width() - _surface.stringWidth(line2) - 2) / 2;
+ yp = _surface.stringHeight(line2) + 2;
+ _surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP);
+ }
+}
+
+void WidgetInventoryTooltip::handleEvents() {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ Inventory &inv = *_vm->_inventory;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Scene &scene = *_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ Common::String str;
+ int select = -1, oldSelect = 999;
+ Common::String strWith = fixedText.getText(kFixedText_With);
+ Common::String strUse = fixedText.getText(kFixedText_Use);
+
+ // If we are using an inventory item on an object in the room, display the appropriate text above the mouse cursor
+ if (_owner->_invVerbMode == 3) {
+ select = ui._bgFound;
+ oldSelect = ui._oldBgFound;
+
+ if (select != -1 && (select != oldSelect || (select != -1 && _surface.empty()))) {
+ // See if we're pointing at a shape or a sprite
+ if (select < 1000) {
+ Object &obj = scene._bgShapes[select];
+
+ if (!obj._description.empty() && !obj._description.hasPrefix(" ")) {
+ if (_vm->getLanguage() == Common::GR_GRE) {
+
+ if (!_owner->_swapItems)
+ str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), obj._description.c_str(),
+ inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
+ else
+ str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
+ obj._description.c_str(), _owner->_verb.c_str());
+ } else {
+ if (_owner->_swapItems)
+ str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), obj._description.c_str(), _owner->_action.c_str(),
+ inv[_owner->_invSelect]._name.c_str());
+ else
+ str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), inv[_owner->_invSelect]._name.c_str(),
+ _owner->_action.c_str(), obj._description.c_str());
+ }
+ }
+ } else {
+ Person &person = people[ui._bgFound - 1000];
+
+ if (!person._description.empty() && !person._description.hasPrefix(" ")) {
+ if (_vm->getLanguage() == Common::GR_GRE) {
+ if (!_owner->_swapItems)
+ str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), person._description.c_str(),
+ inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str());
+ else
+ str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(),
+ person._description.c_str(), _owner->_verb.c_str());
+ } else {
+
+ if (_owner->_swapItems)
+ str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), person._description.c_str(),
+ _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str());
+ else
+ str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(),
+ inv[_owner->_invSelect]._name.c_str(), _owner->_action.c_str(), person._description.c_str());
+ }
+ }
+ }
+ }
+ } else {
+ Common::Rect r = _owner->_bounds;
+ r.grow(-3);
+
+ if (r.contains(mousePos)) {
+ select = (mousePos.x - r.left) / (INVENTORY_XSIZE + 3) + NUM_INVENTORY_SHOWN / 2 *
+ ((mousePos.y - r.top) / (INVENTORY_YSIZE + 3)) + inv._invIndex;
+
+ if (select >= inv._holdings) {
+ select = -1;
+ } else {
+ oldSelect = _owner->_invSelect;
+
+ if (select != _owner->_invSelect || _surface.empty()) {
+
+ if (_owner->_invMode == 1) {
+ // See if we were pointing at a shapre or sprite
+ if (ui._activeObj < 1000) {
+ Object &obj = scene._bgShapes[ui._activeObj];
+
+ if (!obj._description.empty() && !obj._description.hasPrefix(" "))
+ str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
+ strWith.c_str(), obj._description.c_str());
+ } else {
+ Person &person = people[ui._activeObj - 1000];
+
+ if (!person._description.empty() && !person._description.hasPrefix(" "))
+ str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(),
+ strWith.c_str(), person._description.c_str());
+ }
+ } else {
+ if (_owner->_invVerbMode == 2)
+ str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[_owner->_invSelect]._name.c_str(),
+ strWith.c_str(), inv[select]._name.c_str());
+ else
+ str = inv[select]._description.c_str();
+ }
+ }
+ }
+ }
+ }
+
+ // See if they are pointing at a different inventory object and we need to
+ // change the graphics of the Text Tag
+ if (select != oldSelect || (select != -1 && _surface.empty())) {
+ // Set the text
+ setText(str);
+
+ if (_owner->_invVerbMode != 3)
+ _owner->_invSelect = select;
+ else
+ ui._oldBgFound = select;
+ } else if (select == -1 && oldSelect != -1) {
+ setText(Common::String());
+ return;
+ }
+
+ if (_owner->_invVerbMode == 3)
+ // Adjust tooltip to be above the inventory item being shown above the standard cursor
+ mousePos.y -= events._hotspotPos.y;
+
+ // Update the position of the tooltip
+ int xs = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCENE_WIDTH - _bounds.width());
+ int ys = CLIP(mousePos.y - _bounds.height(), 0, SHERLOCK_SCREEN_HEIGHT - _bounds.height());
+ _bounds.moveTo(xs, ys);
+}
+
+/*----------------------------------------------------------------*/
+
+WidgetInventoryVerbs::WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner) :
+ WidgetBase(vm), _owner(owner) {
+ _invVerbSelect = _oldInvVerbSelect = -1;
+}
+
+void WidgetInventoryVerbs::load() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+
+ // Make the Verb List for this Inventory Item
+ _inventCommands.clear();
+ _inventCommands.push_back(FIXED(Look));
+
+ // Default the Action word to "with"
+ _owner->_action = _vm->getLanguage() == Common::GR_GRE ? "" : FIXED(With);
+
+ // Search all the bgshapes for any matching Target Fields
+ for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
+ Object &obj = scene._bgShapes[idx];
+
+ if (obj._type != INVALID && obj._type != HIDDEN) {
+ for (int useNum = 0; useNum < 6; ++useNum) {
+ if (!obj._use[useNum]._verb.hasPrefix("*") &&
+ !obj._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name)) {
+ // Make sure the Verb is not already in the list
+ bool found1 = false;
+ for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
+ if (!_inventCommands[cmdNum].compareToIgnoreCase(obj._use[useNum]._verb))
+ found1 = true;
+ }
+
+ if (!found1) {
+ _inventCommands.push_back(obj._use[useNum]._verb);
+
+ // Check for any Special Action commands
+ for (int nameNum = 0; nameNum < 4; ++nameNum) {
+ if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*V", 2)) {
+ if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*VSWAP", 6))
+ _owner->_swapItems = true;
+ else
+ _owner->_action = Common::String(obj._use[useNum]._names[nameNum].c_str() + 2);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Search the NPCs for matches as well
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ for (int useNum = 0; useNum < 2; ++useNum) {
+ if (!people[idx]._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name) &&
+ !people[idx]._use[useNum]._verb.empty() && !people[idx]._use[useNum]._verb.hasPrefix(" ")) {
+ bool found1 = false;
+ for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) {
+ if (!_inventCommands[cmdNum].compareToIgnoreCase(people[idx]._use[cmdNum]._verb))
+ found1 = true;
+ }
+
+ if (!found1)
+ _inventCommands.push_back(people[idx]._use[useNum]._verb);
+ }
+ }
+ }
+
+ // Finally see if the item itself has a verb
+ if (!inv[_owner->_invSelect]._verb._verb.empty()) {
+ // Don't add "Solve" to the Foolscap if it's already been "Solved"
+ if (inv[_owner->_invSelect]._verb._verb.compareToIgnoreCase(FIXED(Solve)) || !_vm->readFlags(299))
+ _inventCommands.push_back(inv[_owner->_invSelect]._verb._verb);
+ }
+
+ // Now find the widest command in the _inventCommands array
+ int width = 0;
+ for (uint idx = 0; idx < _inventCommands.size(); ++idx)
+ width = MAX(width, _surface.stringWidth(_inventCommands[idx]));
+
+ // Set up bounds for the menu
+ _bounds = Common::Rect(width + _surface.widestChar() * 2 + 6,
+ (_surface.fontHeight() + 7) * _inventCommands.size() + 3);
+ _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
+
+ // Create the surface
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+ makeInfoArea();
+
+ // Draw the Verb commands and the lines separating them
+ ImageFile &images = *ui._interfaceImages;
+ for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
+ _surface.writeString(_inventCommands[idx], Common::Point((_bounds.width() -
+ _surface.stringWidth(_inventCommands[idx])) / 2, (_surface.fontHeight() + 7) * idx + 5), INFO_TOP);
+
+ if (idx < (int)_inventCommands.size() - 1) {
+ _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.right - 4, INFO_TOP);
+ _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.right - 4, INFO_MIDDLE);
+ _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.right - 4, INFO_BOTTOM);
+
+ _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1)));
+ _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width,
+ (_surface.fontHeight() + 7) * (idx + 1) - 1));
+ }
+ }
+
+ summonWindow();
+}
+
+void WidgetInventoryVerbs::handleEvents() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Common::Point mousePos = events.mousePos();
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ TattooEngine &vm = *(TattooEngine *)_vm;
+
+ // Handle changing highlighted verb entry
+ highlightControls();
+
+ // See if they want to close the menu (by clicking outside the menu)
+ Common::Rect innerBounds = _bounds;
+ innerBounds.grow(-3);
+
+ // Flag is they started pressing outside of the menu
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ ui._scrollHighlight = SH_NONE;
+ banishWindow();
+
+ if ((_outsideMenu && !innerBounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _owner->_invVerbMode = 0;
+ } else if (innerBounds.contains(mousePos)) {
+ _outsideMenu = false;
+
+ // Check if they are trying to solve the Foolscap puzzle, or looking at the completed puzzle
+ bool doHangman = !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) &&
+ !_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Solve));
+ doHangman |= (!inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
+ && _inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Look)) && vm.readFlags(299);
+
+ if (doHangman) {
+ // Close the entire Inventory and return to Standard Mode
+ _owner->_invVerbMode = 0;
+
+ _owner->_tooltipWidget.banishWindow();
+ inv.freeInv();
+
+ events.clearEvents();
+ events.setCursor(ARROW);
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+
+ scene.doBgAnim();
+ vm.doHangManPuzzle();
+ } else if (_invVerbSelect == 0) {
+ // They have released the mouse on the Look Verb command, so Look at the inventory item
+ ui._invLookFlag = true;
+ inv.freeInv();
+ ui._windowOpen = false;
+ ui._lookPos = mousePos;
+ ui.printObjectDesc(inv[_owner->_invSelect]._examine, true);
+ } else {
+ _owner->_invVerbMode = 3;
+ ui._oldBgFound = -1;
+
+ // See if the selected Verb with the selected Iventory Item, is to be used by itself
+ if (!_inventCommands[_invVerbSelect].compareToIgnoreCase(inv[_owner->_invSelect]._verb._verb) ||
+ !inv[_owner->_invSelect]._verb._target.compareToIgnoreCase("*SELF")) {
+ inv.freeInv();
+
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ events.clearEvents();
+ ui.checkAction(inv[_owner->_invSelect]._verb, 2000);
+ } else {
+ _owner->_verb = _inventCommands[_invVerbSelect];
+ }
+
+ // If we are still in Inventory Mode, setup the graphic to float in front of the mouse cursor
+ if (ui._menuMode == INV_MODE) {
+ // Add the inventory item to the cursor
+ ImageFrame &imgFrame = (*inv._invShapes[_owner->_invSelect - inv._invIndex])[0];
+ events.setCursor(ARROW, imgFrame._frame);
+
+ // Close the inventory dialog without banishing it, so it can keep getting events
+ // to handle tooltips and actually making the selection of what object to use them item on
+ inv.freeInv();
+ _owner->_surface.free();
+ }
+ }
+ }
+ }
+}
+
+void WidgetInventoryVerbs::highlightControls() {
+ Events &events = *_vm->_events;
+ Common::Point mousePos = events.mousePos();
+
+ Common::Rect innerBounds = _bounds;
+ innerBounds.grow(-3);
+
+ // Set the highlighted verb
+ _invVerbSelect = -1;
+ if (innerBounds.contains(mousePos))
+ _invVerbSelect = (mousePos.y - _bounds.top - 3) / (_surface.fontHeight() + 7);
+
+ // See if the highlighted verb has changed
+ if (_invVerbSelect != _oldInvVerbSelect) {
+ // Draw the list again, with the new highlighting
+ for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) {
+ byte color = (idx == _invVerbSelect) ? COMMAND_HIGHLIGHTED : INFO_TOP;
+ _surface.writeString(_inventCommands[idx], Common::Point(
+ (_bounds.width() - _surface.stringWidth(_inventCommands[idx])) / 2,
+ (_surface.fontHeight() + 7) * idx + 5), color);
+ }
+
+ _oldInvVerbSelect = _invVerbSelect;
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+WidgetInventory::WidgetInventory(SherlockEngine *vm) : WidgetBase(vm),
+ _tooltipWidget(vm, this), _verbList(vm, this) {
+ _invMode = 0;
+ _invVerbMode = 0;
+ _invSelect = _oldInvSelect = -1;
+ _selector = _oldSelector = -1;
+ _dialogTimer = -1;
+ _swapItems = false;
+}
+
+void WidgetInventory::load(int mode) {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+
+ if (mode == 3) {
+ mode = 2;
+ mousePos = Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2);
+ }
+
+ if (mode != 0)
+ _invMode = mode;
+ _invVerbMode = 0;
+ _invSelect = _oldInvSelect = -1;
+ _selector = _oldSelector = -1;
+ _dialogTimer = -1;
+
+ if (mode == 0) {
+ banishWindow();
+ } else {
+ _bounds = Common::Rect((INVENTORY_XSIZE + 3) * NUM_INVENTORY_SHOWN / 2 + BUTTON_SIZE + 6,
+ (INVENTORY_YSIZE + 3) * 2 + 3);
+ _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
+ }
+
+ // Ensure menu will be on-screen
+ restrictToScreen();
+
+ // Load the inventory data
+ inv.loadInv();
+
+ // Redraw the inventory menu on the widget surface
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+
+ // Draw the window background and then the inventory on top of it
+ makeInfoArea(_surface);
+ drawBars();
+ drawInventory();
+}
+
+void WidgetInventory::drawBars() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ImageFile &images = *ui._interfaceImages;
+ int x;
+
+ _surface.hLine(3, INVENTORY_YSIZE + 3, _bounds.width() - 4, INFO_TOP);
+ _surface.hLine(3, INVENTORY_YSIZE + 4, _bounds.width() - 4, INFO_MIDDLE);
+ _surface.hLine(3, INVENTORY_YSIZE + 5, _bounds.width() - 4, INFO_BOTTOM);
+ _surface.transBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2));
+
+ for (int idx = 1; idx <= NUM_INVENTORY_SHOWN / 2; ++idx) {
+ x = idx * (INVENTORY_XSIZE + 3);
+
+ _surface.vLine(x, 3, _bounds.height() - 4, INFO_TOP);
+ _surface.vLine(x + 1, 3, _bounds.height() - 4, INFO_MIDDLE);
+ _surface.vLine(x + 2, 3, _bounds.height() - 4, INFO_BOTTOM);
+
+ _surface.transBlitFrom(images[6], Common::Point(x - 1, 1));
+ _surface.transBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4));
+ _surface.transBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5));
+ _surface.transBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2));
+ }
+
+ _surface.hLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM);
+}
+
+void WidgetInventory::drawInventory() {
+ Inventory &inv = *_vm->_inventory;
+
+ // TODO: Refactor _invIndex into this widget class
+ for (int idx = 0, itemId = inv._invIndex; idx < NUM_INVENTORY_SHOWN; ++idx, ++itemId) {
+ // Figure out the drawing position
+ Common::Point pt(3 + (INVENTORY_XSIZE + 3) * (idx % (NUM_INVENTORY_SHOWN / 2)),
+ 3 + (INVENTORY_YSIZE + 3) * (idx / (NUM_INVENTORY_SHOWN / 2)));
+
+ // Draw the box to serve as the background for the item
+ _surface.hLine(pt.x + 1, pt.y, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
+ _surface.fillRect(Common::Rect(pt.x, pt.y + 1, pt.x + INVENTORY_XSIZE, pt.y + INVENTORY_YSIZE - 1), TRANSPARENCY);
+ _surface.hLine(pt.x + 1, pt.y + INVENTORY_YSIZE - 1, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY);
+
+ // Draw the item
+ if (itemId < inv._holdings) {
+ ImageFrame &img = (*inv._invShapes[idx])[0];
+ _surface.transBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2,
+ pt.y + (INVENTORY_YSIZE - img._height) / 2));
+ }
+ }
+
+ drawScrollBar(inv._invIndex, NUM_INVENTORY_SHOWN, inv._holdings);
+}
+
+void WidgetInventory::handleEvents() {
+ TattooEngine &vm = *(TattooEngine *)_vm;
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ People &people = *_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+
+ if (_invVerbMode == 1)
+ checkTabbingKeys(MAX_INV_COMMANDS);
+ else if (_invVerbMode == 0)
+ checkInvTabbingKeys();
+
+ if (_invVerbMode != 1)
+ _tooltipWidget.handleEvents();
+
+ // Flag is they started pressing outside of the menu
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ if (_invVerbMode != 3)
+ highlightControls();
+
+ // See if they released a mouse button button
+ if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ _dialogTimer = -1;
+ ui._scrollHighlight = SH_NONE;
+
+ // See if they have a Verb List open for an Inventry Item
+ if (_invVerbMode == 1)
+ return;
+
+ if (_invVerbMode == 3) {
+ // Selecting object after inventory verb has been selected
+ _tooltipWidget.banishWindow();
+ close();
+
+ if (ui._keyState.keycode != Common::KEYCODE_ESCAPE) {
+ // If user pointed at an item, use the selected inventory item with this item
+ bool found = false;
+ if (ui._bgFound != -1) {
+ if (ui._personFound) {
+ for (int idx = 0; idx < 2; ++idx) {
+ if (!people[ui._bgFound - 1000]._use[idx]._verb.compareToIgnoreCase(_verb) &&
+ !people[ui._bgFound - 1000]._use[idx]._target.compareToIgnoreCase(_invTarget)) {
+ ui.checkAction(people[ui._bgFound - 1000]._use[idx], ui._bgFound);
+ found = true;
+ }
+ }
+ } else {
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!ui._bgShape->_use[idx]._verb.compareToIgnoreCase(_verb) &&
+ !ui._bgShape->_use[idx]._target.compareToIgnoreCase(_invTarget)) {
+ ui.checkAction(ui._bgShape->_use[idx], ui._bgFound);
+ found = true;
+ }
+ }
+ }
+ }
+
+ if (!found)
+ ui.putMessage("%s", FIXED(NoEffect));
+ }
+ } else if ((_outsideMenu && !_bounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ // Want to close the window (clicked outside of it). So close the window and return to Standard
+ close();
+
+ } else if (_bounds.contains(mousePos)) {
+ // Mouse button was released inside the inventory window
+ _outsideMenu = false;
+
+ // See if they are pointing at one of the inventory items
+ if (_invSelect != -1) {
+ // See if they are in Use Obj with Inv. Mode (they right clicked on an item
+ // in the room and selected "Use with Inv.")
+ if (_invMode == 1) {
+ _tooltipWidget.banishWindow();
+ banishWindow();
+
+ // See if the item in the room that they started with was a person
+ bool found = false;
+ if (ui._activeObj >= 1000) {
+ // Object was a person, activate anything in his two verb fields
+ for (int idx = 0; idx < 2; ++idx) {
+ if (!people[ui._activeObj - 1000]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
+ ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj);
+ found = true;
+ }
+ }
+ } else {
+ // Object was a regular object, activate anything in its verb fields
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) {
+ ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj);
+ found = true;
+ }
+ }
+ }
+ if (!found)
+ ui.putMessage("%s", FIXED(NoEffect));
+
+ } else {
+ // See if they right clicked on an item
+ if (events._rightReleased) {
+ _invVerbMode = 1;
+ _verbList._oldInvVerbSelect = -1;
+ _tooltipWidget.banishWindow();
+
+ // Keep track of the name of the inventory object so we can check it against the target fields
+ // of verbs when we activate it
+ _invTarget = inv[_invSelect]._name;
+ _swapItems = false;
+
+ _verbList.load();
+ } else {
+ // They left clicked on an inventory item, so Look at it
+
+ // Check if they are looking at the solved Foolscap
+ if ((!inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv7)))
+ && vm.readFlags(299)) {
+ banishWindow();
+ _tooltipWidget.erase();
+
+ _invVerbMode = 0;
+ inv.freeInv();
+
+ events.clearEvents();
+ events.setCursor(ARROW);
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+
+ scene.doBgAnim();
+ vm.doHangManPuzzle();
+ } else {
+ ui._invLookFlag = true;
+ inv.freeInv();
+
+ _tooltipWidget.banishWindow();
+ ui._windowOpen = false;
+ ui._lookPos = mousePos;
+ ui.printObjectDesc(inv[_invSelect]._examine, true);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void WidgetInventory::checkInvTabbingKeys() {
+}
+
+void WidgetInventory::highlightControls() {
+ // TODO
+}
+
+void WidgetInventory::banishWindow() {
+ WidgetBase::banishWindow();
+
+ _verbList.banishWindow();
+}
+
+void WidgetInventory::draw() {
+ WidgetBase::draw();
+ _tooltipWidget.draw();
+}
+
+void WidgetInventory::erase() {
+ WidgetBase::erase();
+ _tooltipWidget.erase();
+}
+
+void WidgetInventory::close() {
+ Events &events = *_vm->_events;
+ Inventory &inv = *_vm->_inventory;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ banishWindow();
+ inv.freeInv();
+ events.clearEvents();
+
+ events.setCursor(ARROW);
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_inventory.h b/engines/sherlock/tattoo/widget_inventory.h
new file mode 100644
index 0000000000..53bc2038e0
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_inventory.h
@@ -0,0 +1,159 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_INVENTORY_H
+#define SHERLOCK_TATTOO_WIDGET_INVENTORY_H
+
+#include "common/scummsys.h"
+#include "sherlock/tattoo/widget_base.h"
+#include "sherlock/tattoo/widget_tooltip.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+#define NUM_INVENTORY_SHOWN 8 // Number of Inventory Items Shown
+
+class WidgetInventory;
+
+class WidgetInventoryTooltip: public WidgetTooltipBase {
+private:
+ WidgetInventory *_owner;
+protected:
+ /**
+ * Overriden from base class, since tooltips have a completely transparent background
+ */
+ virtual void drawBackground() {}
+public:
+ WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner);
+ virtual ~WidgetInventoryTooltip() {}
+
+ /**
+ * Set the text for the tooltip
+ */
+ void setText(const Common::String &str);
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetInventoryVerbs : public WidgetBase {
+private:
+ WidgetInventory *_owner;
+ Common::StringArray _inventCommands;
+
+ void highlightControls();
+public:
+ int _invVerbSelect, _oldInvVerbSelect;
+public:
+ WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner);
+ virtual ~WidgetInventoryVerbs() {}
+
+ void load();
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetInventory: public WidgetBase {
+ friend class WidgetInventoryTooltip;
+ friend class WidgetInventoryVerbs;
+private:
+ int _invVerbMode;
+ int _selector, _oldSelector;
+ int _invSelect, _oldInvSelect;
+ int _dialogTimer;
+ WidgetInventoryTooltip _tooltipWidget;
+ WidgetInventoryVerbs _verbList;
+ bool _swapItems;
+ Surface _menuSurface;
+ Common::String _invTarget;
+
+ /**
+ * Draw the bars within the dialog
+ */
+ void drawBars();
+
+ /**
+ * Check for keys to mouse the mouse within the inventory dialog
+ */
+ void checkInvTabbingKeys();
+
+ /**
+ * Highlights the controls
+ */
+ void highlightControls();
+public:
+ int _invMode;
+ Common::String _action;
+ Common::String _verb;
+public:
+ WidgetInventory(SherlockEngine *vm);
+ virtual ~WidgetInventory() {}
+
+ /**
+ * Load the inventory window
+ */
+ void load(int mode);
+
+ /**
+ * Draw the inventory on the surface
+ */
+ void drawInventory();
+
+ /**
+ * Close the window
+ */
+ void close();
+
+ /**
+ * Handle events whilst the widget is on-screen
+ */
+ virtual void handleEvents();
+
+ /**
+ * Close a currently active menu
+ */
+ virtual void banishWindow();
+
+ /**
+ * Erase any previous display of the widget on the screen
+ */
+ virtual void erase();
+
+ /**
+ * Update the display of the widget on the screen
+ */
+ virtual void draw();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_lab.cpp b/engines/sherlock/tattoo/widget_lab.cpp
new file mode 100644
index 0000000000..cc0bae0e90
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_lab.cpp
@@ -0,0 +1,193 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_lab.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+WidgetLab::WidgetLab(SherlockEngine *vm) : WidgetBase(vm) {
+ _labObject = nullptr;
+}
+
+void WidgetLab::summonWindow() {
+ WidgetBase::summonWindow();
+ _labObject = nullptr;
+}
+
+void WidgetLab::handleEvents() {
+ Events &events = *_vm->_events;
+ Scene &scene = *_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ WidgetBase::handleEvents();
+ bool noDesc = false;
+
+ // Handle drawing tooltips. If the user is dragging a lab item, display a tooltip for using the item
+ // on another. Otherwise, fall back on showing standard tooltips
+ if (events.getCursor() == INVALID_CURSOR)
+ displayLabNames();
+ else
+ ui.displayObjectNames();
+
+ // See if they've released a mouse button to do an action
+ if (events._released || events._rightReleased) {
+ // See if the mouse was released in an exit/arrow zone (ie. the "Exit" button)
+ ui._exitZone = -1;
+ if (ui._arrowZone != -1 && events._released)
+ ui._exitZone = ui._arrowZone;
+
+ // Turn any current tooltip off
+ if (ui._arrowZone == -1 || events._rightReleased)
+ ui._tooltipWidget.setText("");
+
+ if (ui._bgFound != -1) {
+ if (ui._bgShape->_description.hasPrefix(" ") || ui._bgShape->_description.empty())
+ noDesc = true;
+ } else {
+ noDesc = true;
+ }
+
+ if (events._rightReleased) {
+ // If the player is dragging an object around, restore it to its previous location and reset the cursor
+ if (_labObject) {
+ _labObject->toggleHidden();
+
+ // Toggle any other objects (like shadows) tied to this object
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
+ for (int nameNum = 0; nameNum < 4; ++nameNum)
+ scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
+ }
+ }
+
+ events.setCursor(ARROW);
+ }
+
+ // Show the command list for this object
+ ui._verbsWidget.load(!noDesc);
+ } else if (!noDesc) {
+ // The player has released on an object, see if they had an object selected
+ // that will be used with this new object
+ if (_labObject) {
+ // See if the dragged object can be used with the new object
+ for (int idx = 0; idx < 6; ++idx) {
+ // See if the name of the dragged object is in any of the Target
+ // fields of the verbs for the new object
+ if (!_labObject->_name.compareToIgnoreCase(ui._bgShape->_use[idx]._target.c_str())) {
+ // This object can be used, so use it
+ ui.checkAction(ui._bgShape->_use[idx], ui._bgFound);
+ ui._activeObj = -1;
+ }
+ }
+
+ // Restore the dragged object to its previous location
+ _labObject->toggleHidden();
+
+ // Toggle any other objects (like shadows) tied to this object
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
+ for (int nameNum = 0; nameNum < 4; ++nameNum)
+ scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
+ }
+ }
+ } else if (!ui._bgShape->_name.compareToIgnoreCase("Exit")) {
+ // Eexecut the Exit button's script, which will leave the scene
+ ui.lookAtObject();
+ }
+ } else {
+ // The player has released the mouse while NOT over an object. If theu were dragging an object
+ // around with the mouse, restore it to its previous location and reset the cursor
+ if (_labObject) {
+ _labObject->toggleHidden();
+
+ // Toggle any other objects (like shadows) tied to this object
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
+ for (int nameNum = 0; nameNum < 4; ++nameNum)
+ scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
+ }
+ }
+
+ events.setCursor(ARROW);
+ }
+ }
+ } else if (events._pressed) {
+ if (!_labObject) {
+ // If the mouse is over an object and the object is not SOLID, then we need to pick this object
+ // up so the player can move it around
+ if (ui._bgFound != -1) {
+ // Check if the object is set as SOLID, you can't pick up Solid items
+ if (ui._bgShape->_aType != SOLID && ui._bgShape->_type != NO_SHAPE) {
+ // Save a reference to the object about to be dragged
+ _labObject = ui._bgShape;
+
+ // Set the mouse cursor to the object
+ Graphics::Surface &img = _labObject->_imageFrame->_frame;
+ events.setCursor(img, img.w / 2, img.h / 2);
+
+ // Hide this object until they are done with it (releasing it)
+ _labObject->toggleHidden();
+
+ // Toggle any other objects (like shadows) tied to this object
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) {
+ for (int nameNum = 0; nameNum < 4; ++nameNum)
+ scene.toggleObject(_labObject->_use[idx]._names[nameNum]);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void WidgetLab::displayLabNames() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+
+ // See if thay are pointing at a different object and we need to change the tooltip
+ if (ui._bgFound != ui._oldBgFound) {
+ // See if there is a new object to be displayed
+ if (ui._bgFound == -1) {
+ ui._tooltipWidget.setText("");
+ } else {
+ Common::String str = Common::String::format("%s %s %s %s", FIXED(Use), _labObject->_description.c_str(),
+ FIXED(With), ui._bgShape->_description.c_str());
+
+ // Make sure that the Object has a name
+ if (!ui._bgShape->_description.empty() && !ui._bgShape->_description.hasPrefix(" ")) {
+ ui._tooltipWidget.setText(str);
+ } else {
+ ui._tooltipWidget.setText("");
+ }
+ }
+ }
+
+ ui._oldArrowZone = ui._arrowZone;
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_lab.h b/engines/sherlock/tattoo/widget_lab.h
new file mode 100644
index 0000000000..2f19200b81
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_lab.h
@@ -0,0 +1,66 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_LAB_H
+#define SHERLOCK_TATTOO_WIDGET_LAB_H
+
+#include "common/scummsys.h"
+#include "sherlock/tattoo/widget_base.h"
+#include "sherlock/objects.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+class WidgetLab: public WidgetBase {
+private:
+ Object *_labObject;
+
+ /**
+ * Display tooltips of an object being dragged along with any object the dragged
+ * object is currently over
+ */
+ void displayLabNames();
+public:
+ Common::String _remainingText;
+public:
+ WidgetLab(SherlockEngine *vm);
+ virtual ~WidgetLab() {}
+
+ /**
+ * Summon the window
+ */
+ virtual void summonWindow();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_talk.cpp b/engines/sherlock/tattoo/widget_talk.cpp
new file mode 100644
index 0000000000..5190773dd1
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_talk.cpp
@@ -0,0 +1,529 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_talk.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_journal.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_talk.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define STATEMENT_NUM_X 6
+#define NUM_VISIBLE_TALK_LINES 6
+
+WidgetTalk::WidgetTalk(SherlockEngine *vm) : WidgetBase(vm) {
+ _talkScrollIndex = 0;
+ _selector = _oldSelector = -1;
+ _talkTextX = 0;
+ _dialogTimer = 0;
+}
+
+void WidgetTalk::getTalkWindowSize() {
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+ int width, height;
+
+ // See how many statements are going to be available
+ int numStatements = 0;
+ for (uint idx = 0; idx < talk._statements.size(); ++idx) {
+ if (talk._statements[idx]._talkMap != -1)
+ ++numStatements;
+ }
+
+ width = SHERLOCK_SCREEN_WIDTH * 2 / 3;
+
+ // Split up the questions into separate strings for each line
+ _bounds = Common::Rect(width, 1);
+ setStatementLines();
+
+ // Make sure that the window does not get too big
+ if (_statementLines.size() < 7) {
+ height = (_surface.fontHeight() + 1) * _statementLines.size() + 9;
+ _scroll = false;
+ } else {
+ // Set up the height to a constrained amount, and add extra width for the scrollbar
+ width += BUTTON_SIZE + 3;
+ height = (_surface.fontHeight() + 1) * 6 + 9;
+ _scroll = true;
+ }
+
+ _bounds = Common::Rect(width, height);
+}
+
+void WidgetTalk::load() {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+
+ // Figure out the window size
+ getTalkWindowSize();
+
+ // Place the window centered above the player
+ Common::Point pt;
+ int scaleVal = scene.getScaleVal(people[HOLMES]._position);
+ pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
+
+ if (scaleVal == SCALE_THRESHOLD) {
+ pt.x += people[0].frameWidth() / 2;
+ pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()
+ - _bounds.height() - _surface.fontHeight();
+ } else {
+ pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2;
+ pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal)
+ - _bounds.height() - _surface.fontHeight();
+ }
+
+ _bounds.moveTo(pt);
+
+ // Set up the surface
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+
+ // Form the background for the new window
+ makeInfoArea();
+}
+
+void WidgetTalk::handleEvents() {
+ Events &events = *_vm->_events;
+ TattooJournal &journal = *(TattooJournal *)_vm->_journal;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Sound &sound = *_vm->_sound;
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ Common::KeyCode keycode = ui._keyState.keycode;
+ bool hotkey = false;
+ bool callParrotFile = false;
+
+ // Handle scrollbar events
+ ScrollHighlight oldHighlight = ui._scrollHighlight;
+ handleScrollbarEvents(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
+
+ // If the highlight has changed, redraw the scrollbar
+ if (ui._scrollHighlight != oldHighlight)
+ render(HL_SCROLLBAR_ONLY);
+
+ if (ui._scrollHighlight != SH_NONE || keycode == Common::KEYCODE_HOME || keycode == Common::KEYCODE_END
+ || keycode == Common::KEYCODE_PAGEUP || keycode == Common::KEYCODE_PAGEDOWN) {
+ int scrollIndex = _talkScrollIndex;
+
+ // Check for the scrollbar
+ if (ui._scrollHighlight == SH_THUMBNAIL) {
+ int yp = mousePos.y;
+ yp = CLIP(yp, _bounds.top + BUTTON_SIZE + 3, _bounds.bottom - BUTTON_SIZE - 3);
+
+ // Calculate the line number that corresponds to the position that the mouse is on the scrollbar
+ int lineNum = (yp - _bounds.top - BUTTON_SIZE - 3) * 100 / (_bounds.height() - BUTTON_SIZE * 2 - 6)
+ * _statementLines.size() / 100 - 3;
+
+ // If the new position would place part of the text outsidethe text window, adjust it so it doesn't
+ if (lineNum < 0)
+ lineNum = 0;
+ else if (lineNum + NUM_VISIBLE_TALK_LINES > (int)_statementLines.size()) {
+ lineNum = (int)_statementLines.size() - NUM_VISIBLE_TALK_LINES;
+
+ // Make sure it's not below zero now
+ if (lineNum < 0)
+ lineNum = 0;
+ }
+
+ _talkScrollIndex = lineNum;
+ }
+
+ // Get the current frame so we can check the scroll timer against it
+ uint32 frameNum = events.getFrameCounter();
+
+ if (frameNum > _dialogTimer) {
+ // Set the timeout for the next scroll if the mouse button remains held down
+ _dialogTimer = (_dialogTimer == 0) ? frameNum + NUM_VISIBLE_TALK_LINES : frameNum + 1;
+
+ // Check for Scroll Up
+ if (ui._scrollHighlight == SH_SCROLL_UP && _talkScrollIndex)
+ --_talkScrollIndex;
+
+ // Check for Page Up
+ if ((ui._scrollHighlight == SH_PAGE_UP || keycode == Common::KEYCODE_PAGEUP) && _talkScrollIndex)
+ _talkScrollIndex -= NUM_VISIBLE_TALK_LINES;
+
+ // Check for Page Down
+ if ((ui._scrollHighlight == SH_PAGE_DOWN || keycode == Common::KEYCODE_PAGEDOWN)
+ && (_talkScrollIndex + NUM_VISIBLE_TALK_LINES < (int)_statementLines.size())) {
+ _talkScrollIndex += 6;
+ if (_talkScrollIndex + NUM_VISIBLE_TALK_LINES >(int)_statementLines.size())
+ _talkScrollIndex = _statementLines.size() - NUM_VISIBLE_TALK_LINES;
+ }
+
+ // Check for Scroll Down
+ if (ui._scrollHighlight == SH_SCROLL_DOWN && (_talkScrollIndex + NUM_VISIBLE_TALK_LINES < (int)_statementLines.size()))
+ _talkScrollIndex++;
+ }
+
+ if (keycode == Common::KEYCODE_END)
+ _talkScrollIndex = _statementLines.size() - NUM_VISIBLE_TALK_LINES;
+
+ if (_talkScrollIndex < 0 || keycode == Common::KEYCODE_HOME)
+ _talkScrollIndex = 0;
+
+ // Only redraw the window if the the scrollbar position has changed
+ if (scrollIndex != _talkScrollIndex) {
+ _surface.fillRect(Common::Rect(4, 5, _surface.w() - BUTTON_SIZE - 8, _surface.h() - 4), TRANSPARENCY);
+ render(HL_NO_HIGHLIGHTING);
+ }
+ }
+
+ // Flag if they started pressing outside of the window
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ // Check for which statement they are pointing at
+ _selector = -1;
+ if (ui._scrollHighlight == SH_NONE) {
+ if (Common::Rect(_bounds.left, _bounds.top + 5, _bounds.right - 3, _bounds.bottom - 5).contains(mousePos)) {
+ if (_scroll) {
+ // Disregard the scrollbar when setting the statement number
+ if (!Common::Rect(_bounds.right - BUTTON_SIZE, _bounds.top, _bounds.right, _bounds.bottom).contains(mousePos))
+ _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
+ } else {
+ _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1);
+ }
+
+ // Now translate the line number of the displayed line into the appropriate
+ // Statement number or set it to 255 to indicate no Statement selected
+ if (_selector >= 0 && _selector < (int)_statementLines.size())
+ _selector = _statementLines[_selector]._num;
+ else
+ _selector = -1;
+ }
+ }
+
+ // Check for the tab keys
+ if (keycode == Common::KEYCODE_TAB && ui._scrollHighlight == SH_NONE) {
+ if (_selector == -1) {
+ _selector = _statementLines[_scroll ? _talkScrollIndex : 0]._num;
+
+ events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 10, _bounds.top + _surface.fontHeight() + 2));
+ } else {
+ if (ui._keyState.flags & Common::KBD_SHIFT) {
+ _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
+ if (_statementLines[_selector]._num == _statementLines[_talkScrollIndex]._num) {
+ _selector = (_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex;
+ } else {
+ int idx = _selector;
+ do {
+ --_selector;
+ } while (_selector > 0 && _statementLines[idx]._num == _statementLines[_selector]._num);
+ }
+
+ int idx = _selector;
+ while ((_statementLines[idx]._num == _statementLines[_selector - 1]._num) && (_selector > _talkScrollIndex))
+ --_selector;
+ } else {
+ _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex;
+ if (_statementLines[_selector]._num == _statementLines[(_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex]._num) {
+ _selector = _talkScrollIndex;
+ } else {
+ int idx = _selector;
+ do {
+ ++_selector;
+ } while (_selector < (int)_statementLines.size() && _statementLines[idx]._num == _statementLines[_selector]._num);
+ }
+ }
+
+ events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() + 2 + (_surface.fontHeight() + 1)
+ * (_selector - _talkScrollIndex)));
+ _selector = _statementLines[_selector]._num;
+ }
+ }
+
+ // Handle selecting a talk entry if a numeric key has been pressed
+ if (keycode >= Common::KEYCODE_1 && keycode <= Common::KEYCODE_9) {
+ int x = 0, t = 0, y = 0;
+
+ do {
+ if (y == (keycode - Common::KEYCODE_1)) {
+ _selector = _statementLines[t]._num;
+ _outsideMenu = false;
+ hotkey = true;
+ break;
+ }
+
+ ++t;
+ if (_statementLines[x]._num != _statementLines[t]._num) {
+ x = t;
+ ++y;
+ }
+ } while (t < (int)_statementLines.size());
+ }
+
+ // Display the selected statement highlighted and reset the last statement.
+ if (_selector != _oldSelector) {
+ render(HL_CHANGED_HIGHLIGHTS);
+ _oldSelector = _selector;
+ }
+
+ if (events._released || events._rightReleased || keycode == Common::KEYCODE_ESCAPE || hotkey) {
+ events.clearEvents();
+ _dialogTimer = 0;
+ ui._scrollHighlight = SH_NONE;
+
+ // See if they want to close the menu (click outside the window or Escape pressed)
+ if ((_outsideMenu && !_bounds.contains(mousePos)) || keycode == Common::KEYCODE_ESCAPE) {
+ if (keycode == Common::KEYCODE_ESCAPE)
+ _selector = -1;
+
+ talk.freeTalkVars();
+ talk.pullSequence();
+
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER) {
+ while (!people[idx]._pathStack.empty())
+ people[idx].pullNPCPath();
+ }
+ }
+
+ banishWindow();
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+
+ if (scene._currentScene == 52)
+ callParrotFile = true;
+ }
+
+ _outsideMenu = false;
+
+ // See if they have selected a statement to say
+ if (_selector != -1) {
+ if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
+ journal.record(talk._converseNum, _selector);
+ talk._talkHistory[talk._converseNum][_selector] = true;
+
+ banishWindow();
+ talk._speaker = _vm->readFlags(FLAG_PLAYER_IS_HOLMES) ? HOLMES : WATSON;
+ _scroll = false;
+ const byte *msg = (const byte *)talk._statements[_selector]._statement.c_str();
+ talk.talkInterface(msg);
+
+ if (sound._speechOn)
+ sound._talkSoundFile += Common::String::format("%02dA", _selector + 1);
+
+ int msgLen = MAX((int)talk._statements[_selector]._statement.size(), 160);
+ people.setTalkSequence(talk._speaker);
+
+ talk.waitForMore(msgLen);
+ if (talk._talkToAbort)
+ return;
+
+ people.setListenSequence(talk._speaker);
+
+ do {
+ talk._scriptSelect = _selector;
+ talk._speaker = talk._talkTo;
+
+ // Make a copy of the reply (since talkTo can reload the statements list), and call talkTo
+ Common::String reply = talk._statements[_selector]._reply;
+ talk.doScript(reply);
+
+ // Reset the misc field in case any people changed their sequences
+ for (int idx = 0; idx < MAX_CHARACTERS; ++idx)
+ people[idx]._misc = 0;
+
+ if (!talk._talkToAbort) {
+ if (!talk._statements[_selector]._modified.empty()) {
+ for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx)
+ _vm->setFlags(talk._statements[_selector]._modified[idx]);
+
+ talk.setTalkMap();
+ }
+
+ // See if there is another talk file linked to this.
+ if (!talk._statements[_selector]._linkFile.empty() && !talk._scriptMoreFlag) {
+ Common::String linkFile = talk._statements[_selector]._linkFile;
+ talk.freeTalkVars();
+ talk.loadTalkFile(linkFile);
+
+ _talkScrollIndex = 0;
+ int select = -1;
+ _selector = _oldSelector = -1;
+
+ // Find the first statement that has all it's flags set correctly
+ for (uint idx = 0; idx < talk._statements.size() && select == -1; ++select) {
+ if (!talk._statements[idx]._talkMap)
+ select = idx;
+ }
+
+ if (select == -1) {
+ talk.freeTalkVars();
+ ui.putMessage("%s", FIXED(NothingToSay));
+ return;
+ }
+
+ // See is the new statement is in stealth mode
+ talk._talkStealth = (talk._statements[select]._statement.hasPrefix("^")) ? 2 : 0;
+
+ // See if the new file is a standard file, a reply first file, or a Stealth Mode file
+ if (!talk._statements[select]._statement.hasPrefix("*") && !talk._statements[select]._statement.hasPrefix("^")) {
+ load();
+ summonWindow();
+
+ setStatementLines();
+ render(HL_NO_HIGHLIGHTING);
+ break;
+ } else {
+ _selector = select;
+
+ if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal)
+ journal.record(talk._converseNum, _selector);
+
+ talk._talkHistory[talk._converseNum][_selector] = true;
+ }
+ } else {
+ talk.freeTalkVars();
+ talk.pullSequence();
+
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER)
+ while (!people[idx]._pathStack.empty())
+ people[idx].pullNPCPath();
+ }
+
+ ui.banishWindow();
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ break;
+ }
+ } else {
+ break;
+ }
+ } while (!_vm->shouldQuit());
+
+ events.clearEvents();
+
+ // Now, if a script was pushed onto the script stack, restore them to allow the previous script to continue.
+ talk.popStack();
+ }
+ }
+
+ if (callParrotFile)
+ talk.talkTo("POUT52A");
+}
+
+void WidgetTalk::render(Highlight highlightMode) {
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+ int yp = 5;
+ int statementNum = 1;
+ byte color;
+
+ if (highlightMode != HL_SCROLLBAR_ONLY) {
+ // Draw all the statements
+ // Check whether scrolling has occurred, and if so, figure out what the starting
+ // number for the first visible statement will be
+ if (_talkScrollIndex) {
+ for (int idx = 1; idx <= _talkScrollIndex; ++idx) {
+ if (_statementLines[idx - 1]._num != _statementLines[idx]._num)
+ ++statementNum;
+ }
+ }
+
+ // Main drawing loop
+ for (uint idx = _talkScrollIndex; idx < _statementLines.size() && yp < (_bounds.height() - _surface.fontHeight()); ++idx) {
+ if (highlightMode == HL_NO_HIGHLIGHTING || _statementLines[idx]._num == _selector ||
+ _statementLines[idx]._num == _oldSelector) {
+ // Different coloring based on whether the option has been previously chosen or not
+ color = (!talk._talkHistory[talk._converseNum][_statementLines[idx]._num]) ?
+ INFO_TOP : INFO_BOTTOM;
+
+ if (_statementLines[idx]._num == _selector && highlightMode == HL_CHANGED_HIGHLIGHTS)
+ color = COMMAND_HIGHLIGHTED;
+
+ // See if it's the start of a new statement, so needs the statement number to be displayed
+ if (!idx || _statementLines[idx]._num != _statementLines[idx - 1]._num) {
+ Common::String numStr = Common::String::format("%d.", statementNum);
+ _surface.writeString(numStr, Common::Point(STATEMENT_NUM_X, yp), color);
+ }
+
+ // Display the statement line
+ _surface.writeString(_statementLines[idx]._line, Common::Point(_talkTextX, yp), color);
+ }
+ yp += _surface.fontHeight() + 1;
+
+ // If the next line starts a new statement, then increment the statement number
+ if (idx == (_statementLines.size() - 1) || _statementLines[idx]._num != _statementLines[idx + 1]._num)
+ ++statementNum;
+ }
+ }
+
+ // See if the scroll bar needs to be drawn
+ if (_scroll && highlightMode != HL_CHANGED_HIGHLIGHTS)
+ drawScrollBar(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size());
+}
+
+void WidgetTalk::setStatementLines() {
+ TattooTalk &talk = *(TattooTalk *)_vm->_talk;
+ const char *numStr = "19.";
+
+ // See how many statements are going to be available
+ int numStatements = 0;
+ for (uint idx = 0; idx < talk._statements.size(); ++idx) {
+ if (talk._statements[idx]._talkMap != -1)
+ ++numStatements;
+ }
+
+ // If there are more lines than can be displayed in the interface window at one time, adjust the allowed
+ // width to take into account needing a scrollbar
+ int xSize = _scroll ? _bounds.width() - BUTTON_SIZE - 3 : _bounds.width();
+
+ // Also adjust the width to allow room for the statement numbers at the left edge of the display
+ int n = (numStatements < 10) ? 1 : 0;
+ xSize -= _surface.stringWidth(numStr + n) + _surface.widestChar() / 2 + 9;
+ _talkTextX = _surface.stringWidth(numStr + n) + _surface.widestChar() / 4 + 6;
+ _statementLines.clear();
+
+ for (uint statementNum = 0; statementNum < talk._statements.size(); ++statementNum) {
+ // See if this statment meets all of it's flag requirements
+ if (talk._statements[statementNum]._talkMap != -1) {
+ // Get the next statement text to process
+ Common::String str = talk._statements[statementNum]._statement;
+
+ Common::StringArray statementLines;
+ splitLines(str, statementLines, xSize, 999);
+
+ // Add the lines in
+ for (uint idx = 0; idx < statementLines.size(); ++idx)
+ _statementLines.push_back(StatementLine(statementLines[idx], statementNum));
+ }
+ }
+}
+
+void WidgetTalk::refresh() {
+ _talkScrollIndex = 0;
+ _selector = _oldSelector = -1;
+
+ setStatementLines();
+ render(HL_NO_HIGHLIGHTING);
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_talk.h b/engines/sherlock/tattoo/widget_talk.h
new file mode 100644
index 0000000000..1dbbc22a69
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_talk.h
@@ -0,0 +1,92 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_TALK_H
+#define SHERLOCK_TATTOO_WIDGET_TALK_H
+
+#include "common/scummsys.h"
+#include "sherlock/tattoo/widget_base.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+enum Highlight { HL_NO_HIGHLIGHTING, HL_CHANGED_HIGHLIGHTS, HL_SCROLLBAR_ONLY };
+
+/**
+ * Handles displaying a dialog with conversation options the player can select from
+ */
+class WidgetTalk: public WidgetBase {
+ struct StatementLine {
+ Common::String _line;
+ int _num;
+
+ StatementLine() : _num(0) {}
+ StatementLine(const Common::String &line, int num) : _line(line), _num(num) {}
+ };
+private:
+ int _talkScrollIndex;
+ Common::Array<StatementLine> _statementLines;
+ int _selector, _oldSelector;
+ int _talkTextX;
+ uint32 _dialogTimer;
+
+ void getTalkWindowSize();
+
+ /**
+ * Re-renders the contenst of the window to the widget's surface
+ */
+ void render(Highlight highlightMode);
+
+ /**
+ * This initializes the _statementLines array, which contains the talk options split up line
+ * by line, as well as which statement a particular line is part of.
+ */
+ void setStatementLines();
+public:
+ WidgetTalk(SherlockEngine *vm);
+ virtual ~WidgetTalk() {}
+
+ /**
+ * Figures out how many lines the available talk lines will take up, and opens a text window
+ * of appropriate size
+ */
+ void load();
+
+ /**
+ * Refresh the talk display
+ */
+ void refresh();
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_text.cpp b/engines/sherlock/tattoo/widget_text.cpp
new file mode 100644
index 0000000000..33330df2e2
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_text.cpp
@@ -0,0 +1,223 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_text.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+WidgetText::WidgetText(SherlockEngine *vm) : WidgetBase(vm) {
+}
+
+void WidgetText::load(const Common::String &str, int speaker) {
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::StringArray lines;
+
+ int width = SHERLOCK_SCREEN_WIDTH / 3;
+ int height;
+
+ for (;;) {
+ splitLines(str, lines, width - _surface.widestChar() * 2, 100);
+ height = (screen.fontHeight() + 1) * lines.size() + 9;
+
+ if ((width - _surface.widestChar() * 2 > height * 3 / 2) || (width - _surface.widestChar() * 2
+ > SHERLOCK_SCREEN_WIDTH * 3 / 4))
+ break;
+
+ width += (width / 4);
+ }
+
+ // See if it's only a single line long
+ if (height == _surface.fontHeight() + 10) {
+ width = _surface.widestChar() * 2 + 6;
+
+ const char *strP = str.c_str();
+ while (*strP && (*strP < talk._opcodes[OP_SWITCH_SPEAKER] || *strP == talk._opcodes[OP_NULL]))
+ width += _surface.charWidth(*strP++);
+ }
+
+ _bounds = Common::Rect(width, height);
+
+ if (speaker == -1) {
+ // No speaker specified, so center window on look position
+ _bounds.translate(ui._lookPos.x - width / 2, ui._lookPos.y - height / 2);
+ } else {
+ // Speaker specified, so center the window above them
+ centerWindowOnSpeaker(speaker);
+ }
+
+ render(str);
+}
+
+void WidgetText::centerWindowOnSpeaker(int speaker) {
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Common::Point pt;
+
+ bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES);
+ if (people[HOLMES]._type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag))) {
+ // Place the window centered above the player
+ pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
+
+ int scaleVal = scene.getScaleVal(people[HOLMES]._position);
+ if (scaleVal == SCALE_THRESHOLD) {
+ pt.x += people[HOLMES].frameWidth() / 2;
+ pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()
+ - _bounds.height() - _surface.fontHeight();
+ } else {
+ pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2;
+ pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal)
+ - _bounds.height() - _surface.fontHeight();
+ }
+ } else {
+ pt.y = -1;
+
+ // Check each NPC to see if they are the one that is talking
+ for (int idx = 1; idx < MAX_CHARACTERS; ++idx) {
+ if (people[idx]._type == CHARACTER) {
+ if (!scumm_strnicmp(people[idx]._npcName.c_str(), people._characters[speaker]._portrait, 4)) {
+ // Place the window above the player
+ pt.x = people[idx]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2;
+
+ int scaleVal = scene.getScaleVal(people[idx]._position);
+ if (scaleVal == SCALE_THRESHOLD) {
+ pt.x += people[idx].frameWidth() / 2;
+ pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx].frameHeight()
+ - _bounds.height() - _surface.fontHeight();
+ }
+ else {
+ pt.x += people[idx]._imageFrame->sDrawXSize(scaleVal) / 2;
+ pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx]._imageFrame->sDrawYSize(scaleVal)
+ - _bounds.height() - _surface.fontHeight();
+ }
+
+ if (pt.y < 0)
+ pt.y = 0;
+ break;
+ }
+ }
+ }
+
+ if (pt.y == -1) {
+ for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) {
+ Object &obj = scene._bgShapes[idx];
+
+ if (obj._type == ACTIVE_BG_SHAPE && !scumm_strnicmp(obj._name.c_str(), people._characters[speaker]._portrait, 4)) {
+ // Place the window centered above the character
+ pt.x = obj._position.x - _bounds.width() / 2;
+ pt.y = obj._position.y - _bounds.height() - _surface.fontHeight();
+ if (pt.y < 0)
+ pt.y = 0;
+ if (obj._scaleVal == SCALE_THRESHOLD)
+ pt.x += obj.frameWidth() / 2;
+ else
+ pt.x += obj._imageFrame->sDrawXSize(obj._scaleVal) / 2;
+
+ break;
+ }
+ }
+ }
+
+ if (pt.y == -1) {
+ pt.x = SHERLOCK_SCREEN_WIDTH / 2 - _bounds.width() / 2;
+ pt.y = SHERLOCK_SCREEN_HEIGHT / 2 - _bounds.height() / 2;
+ }
+ }
+
+ _bounds.moveTo(pt);
+}
+
+void WidgetText::render(const Common::String &str) {
+ Common::StringArray lines;
+ _remainingText = splitLines(str, lines, _bounds.width() - _surface.widestChar() * 2,
+ _bounds.height() / (_surface.fontHeight() + 1));
+
+ // Allocate a surface for the window
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+
+ // Form the background for the new window
+ makeInfoArea();
+
+ int yp = 5;
+ for (int lineNum = 0; yp < (_bounds.height() - _surface.fontHeight() / 2); ++lineNum) {
+ _surface.writeString(lines[lineNum], Common::Point(_surface.widestChar(), yp), INFO_TOP);
+ yp += _surface.fontHeight() + 1;
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+WidgetMessage::WidgetMessage(SherlockEngine *vm) : WidgetBase(vm) {
+ _menuCounter = 0;
+}
+
+void WidgetMessage::load(const Common::String &str, int time) {
+ Events &events = *_vm->_events;
+ Common::Point mousePos = events.mousePos();
+ _menuCounter = time;
+
+ // Set up the bounds for the dialog to be a single line
+ _bounds = Common::Rect(_surface.stringWidth(str) + _surface.widestChar() * 2 + 6, _surface.fontHeight() + 10);
+ _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
+
+ // Allocate a surface for the window
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+
+ // Form the background for the new window and write the line of text
+ makeInfoArea();
+ _surface.writeString(str, Common::Point(_surface.widestChar() + 3, 5), INFO_TOP);
+}
+
+void WidgetMessage::handleEvents() {
+ Events &events = *_vm->_events;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ WidgetBase::handleEvents();
+
+ --_menuCounter;
+
+ // Check if a mouse or keypress has occurred, or the display counter has expired
+ if (events._pressed || events._released || events._rightPressed || events._rightReleased ||
+ ui._keyState.keycode || !_menuCounter) {
+ // Close the window
+ banishWindow();
+
+ // Reset cursor and switch back to standard mode
+ events.setCursor(ARROW);
+ events.clearEvents();
+ ui._key = -1;
+ ui._oldBgFound = -1;
+ ui._menuMode = STD_MODE;
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_text.h b/engines/sherlock/tattoo/widget_text.h
new file mode 100644
index 0000000000..f027bdae9f
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_text.h
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_TEXT_H
+#define SHERLOCK_TATTOO_WIDGET_TEXT_H
+
+#include "common/scummsys.h"
+#include "sherlock/tattoo/widget_base.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+class WidgetText: public WidgetBase {
+private:
+ /**
+ * Center the area the dialog will be drawn on above a given speaker
+ */
+ void centerWindowOnSpeaker(int speaker);
+
+ /**
+ * Build up the text dialog based on the previously set bounds
+ */
+ void render(const Common::String &str);
+public:
+ Common::String _remainingText;
+public:
+ WidgetText(SherlockEngine *vm);
+ virtual ~WidgetText() {}
+
+ /**
+ * Load the data for the text window
+ */
+ void load(const Common::String &str, int speaker = -1);
+};
+
+class WidgetMessage : public WidgetBase {
+private:
+ int _menuCounter;
+public:
+ WidgetMessage(SherlockEngine *vm);
+ virtual ~WidgetMessage() {}
+
+ /**
+ * Load the data for the text window
+ */
+ void load(const Common::String &str, int time);
+
+ /**
+ * Handle event processing
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_tooltip.cpp b/engines/sherlock/tattoo/widget_tooltip.cpp
new file mode 100644
index 0000000000..b1c13c7a29
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_tooltip.cpp
@@ -0,0 +1,220 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_tooltip.h"
+#include "sherlock/tattoo/tattoo_map.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+#define MAX_TOOLTIP_WIDTH 150
+
+void WidgetTooltipBase::draw() {
+ Screen &screen = *_vm->_screen;
+
+ // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it
+ if (_oldBounds.width() > 0 && _oldBounds != _bounds)
+ erase();
+
+ if (_bounds.width() > 0 && !_surface.empty()) {
+ restrictToScreen();
+
+ // Blit the affected area to the screen
+ screen.slamRect(_bounds);
+
+ // Draw the widget directly onto the screen. Unlike other widgets, we don't draw to the back buffer,
+ // since nothing should be drawing on top of tooltips, so there's no need to store in the back buffer
+ screen.transBlitFrom(_surface, Common::Point(_bounds.left - screen._currentScroll.x,
+ _bounds.top - screen._currentScroll.y));
+
+ // Store a copy of the drawn area for later erasing
+ _oldBounds = _bounds;
+ }
+}
+
+void WidgetTooltipBase::erase() {
+ Screen &screen = *_vm->_screen;
+
+ if (_oldBounds.width() > 0) {
+ // Restore the affected area from the back buffer to the screen
+ screen.slamRect(_oldBounds);
+
+ // Reset the old bounds so it won't be erased again
+ _oldBounds = Common::Rect(0, 0, 0, 0);
+ }
+}
+
+/*----------------------------------------------------------------*/
+
+WidgetTooltip::WidgetTooltip(SherlockEngine *vm) : WidgetTooltipBase (vm) {
+}
+
+void WidgetTooltip::setText(const Common::String &str) {
+ Events &events = *_vm->_events;
+ Common::Point mousePos = events.mousePos();
+ bool reset = false;
+
+ // Make sure that the description is present
+ if (!str.empty()) {
+ int width = _surface.stringWidth(str) + 2;
+ int height = _surface.stringHeight(str) + 2;
+ Common::String line1 = str, line2 = "";
+
+ // See if we need to split it into two lines
+ if (width > MAX_TOOLTIP_WIDTH) {
+ // Go forward word by word to find out where to split the line
+ const char *s = str.c_str();
+ const char *space = nullptr;
+ int dif = 10000;
+
+ for (;;) {
+ // Find end of next word
+ s = strchr(s + 1, ' ');
+
+ if (s == nullptr) {
+ // Reached end of string
+ if (space != nullptr) {
+ line1 = Common::String(str.c_str(), space);
+ line2 = Common::String(space + 1);
+ height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4;
+ }
+ break;
+ }
+
+ // Found space separating words, so see what width the string up to now is
+ Common::String tempLine1 = Common::String(str.c_str(), s);
+ Common::String tempLine2 = Common::String(s + 1);
+ int width1 = _surface.stringWidth(tempLine1);
+ int width2 = _surface.stringWidth(tempLine2);
+
+ // See if we've found a split point that results in a less overall width
+ if (ABS(width1 - width2) < dif) {
+ // Found a better split point
+ dif = ABS(width1 - width2);
+ space = s;
+ line1 = tempLine1;
+ line2 = tempLine2;
+ }
+ }
+ } else {
+ // No line split needed
+ height = _surface.stringHeight(str) + 2;
+ }
+
+ // Reallocate the text surface with the new size
+ _surface.create(width, height);
+ _surface.fill(TRANSPARENCY);
+
+ if (line2.empty()) {
+ // Only a single line
+ _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP);
+ } else {
+ // Two lines to display
+ int xp, yp;
+ xp = (width - _surface.stringWidth(line1) - 2) / 2;
+ _surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP);
+
+ xp = (width - _surface.stringWidth(line2) - 2) / 2;
+ yp = _surface.stringHeight(line1) + 2;
+ _surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP);
+ }
+
+ // Set the initial display position for the tooltip text
+ int tagX = mousePos.x - width / 2;
+ int tagY = mousePos.y - height;
+
+ _bounds = Common::Rect(tagX, tagY, tagX + width, tagY + height);
+ } else {
+ reset = true;
+ }
+
+ if (reset && !_surface.empty()) {
+ _surface.free();
+ }
+}
+
+void WidgetTooltip::handleEvents() {
+ Events &events = *_vm->_events;
+ Common::Point mousePos = events.mousePos();
+
+ // Set the new position for the tooltip
+ int xp = mousePos.x - _bounds.width() / 2;
+ int yp = mousePos.y - _bounds.height();
+
+ _bounds.moveTo(xp, yp);
+}
+
+/*----------------------------------------------------------------*/
+
+void WidgetSceneTooltip::handleEvents() {
+ Events &events = *_vm->_events;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+
+ // See if thay are pointing at a different object and we need to regenerate the tooltip text
+ if (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()) ||
+ ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())) {
+ // See if there is a new object to display text for
+ if ((ui._bgFound != -1 && (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()))) ||
+ (ui._arrowZone != -1 && (ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())))) {
+ Common::String str;
+ if (ui._bgFound != -1) {
+ // Clear the Arrow Zone fields so it won't think we're displaying an Arrow Zone cursor
+ if (scene._currentScene != 90) // RRR Take out the cludge for room 90
+ ui._arrowZone = ui._oldArrowZone = -1;
+
+ // Get the description string
+ str = (ui._bgFound < 1000) ? scene._bgShapes[ui._bgFound]._description :
+ people[ui._bgFound - 1000]._description;
+ } else {
+ // Get the exit zone description
+ str = scene._exits[ui._arrowZone]._dest;
+ }
+
+ setText(str.hasPrefix(" ") ? Common::String() : str);
+ } else if ((ui._bgFound == -1 && ui._oldBgFound != -1) || (ui._arrowZone == -1 && ui._oldArrowZone != -1)) {
+ setText("");
+ }
+
+ ui._oldBgFound = ui._bgFound;
+ } else {
+
+ // Set the new position for the tooltip
+ int tagX = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCREEN_WIDTH - _bounds.width());
+ int tagY = MAX(mousePos.y - _bounds.height(), 0);
+
+ _bounds.moveTo(tagX, tagY);
+ }
+
+ ui._oldArrowZone = ui._arrowZone;
+
+ WidgetTooltip::handleEvents();
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_tooltip.h b/engines/sherlock/tattoo/widget_tooltip.h
new file mode 100644
index 0000000000..a7758d4863
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_tooltip.h
@@ -0,0 +1,87 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_TOOLTIP_H
+#define SHERLOCK_TATTOO_WIDGET_TOOLTIP_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "sherlock/tattoo/widget_base.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+class WidgetTooltipBase : public WidgetBase {
+public:
+ WidgetTooltipBase(SherlockEngine *vm) : WidgetBase(vm) {}
+ virtual ~WidgetTooltipBase() {}
+
+ /**
+ * Erase any previous display of the widget on the screen
+ */
+ virtual void erase();
+
+ /**
+ * Update the display of the widget on the screen
+ */
+ virtual void draw();
+};
+
+class WidgetTooltip: public WidgetTooltipBase {
+public:
+ WidgetTooltip(SherlockEngine *vm);
+ virtual ~WidgetTooltip() {}
+
+ /**
+ * Set the text for the tooltip
+ */
+ void setText(const Common::String &str);
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetSceneTooltip : public WidgetTooltip {
+public:
+ WidgetSceneTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
+
+ /**
+ * Handle updating the tooltip state
+ */
+ virtual void handleEvents();
+};
+
+class WidgetMapTooltip : public WidgetTooltip {
+public:
+ WidgetMapTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {}
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/tattoo/widget_verbs.cpp b/engines/sherlock/tattoo/widget_verbs.cpp
new file mode 100644
index 0000000000..3cdd1247c1
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_verbs.cpp
@@ -0,0 +1,311 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/tattoo/widget_verbs.h"
+#include "sherlock/tattoo/tattoo_fixed_text.h"
+#include "sherlock/tattoo/tattoo_scene.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+#include "sherlock/tattoo/tattoo_people.h"
+#include "sherlock/tattoo/tattoo.h"
+
+namespace Sherlock {
+
+namespace Tattoo {
+
+WidgetVerbs::WidgetVerbs(SherlockEngine *vm) : WidgetBase(vm) {
+ _selector = _oldSelector = -1;
+ _outsideMenu = false;
+}
+
+void WidgetVerbs::load(bool objectsOn) {
+ Events &events = *_vm->_events;
+ TattooPeople &people = *(TattooPeople *)_vm->_people;
+ Talk &talk = *_vm->_talk;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ bool isWatson = false;
+
+ if (talk._talkToAbort)
+ return;
+
+ ui._activeObj = ui._bgFound;
+ _outsideMenu = false;
+ _verbCommands.clear();
+ _selector = _oldSelector = -1;
+
+ // Check if we need to show options for the highlighted object
+ if (objectsOn) {
+ // Set the verb list accordingly, depending on the target being a
+ // person or an object
+ if (ui._personFound) {
+ TattooPerson &person = people[ui._activeObj - 1000];
+ TattooPerson &npc = people[ui._activeObj - 1001];
+
+ if (!scumm_strnicmp(npc._npcName.c_str(), "WATS", 4))
+ isWatson = true;
+
+
+ if (scumm_strnicmp(person._examine.c_str(), "_EXIT", 5))
+ _verbCommands.push_back(FIXED(Look));
+
+ _verbCommands.push_back(FIXED(Talk));
+
+ // Add any extra active verbs from the NPC's verb list
+ for (int idx = 0; idx < 2; ++idx) {
+ if (!person._use[idx]._verb.empty() && !person._use[idx]._verb.hasPrefix(" ") &&
+ (person._use[idx]._target.empty() || person._use[idx]._target.hasPrefix(" "))) {
+ _verbCommands.push_back(person._use[idx]._verb);
+ }
+ }
+ } else {
+ if (!scumm_strnicmp(ui._bgShape->_name.c_str(), "WATS", 4))
+ // Looking at Watson
+ isWatson = true;
+
+ if (scumm_strnicmp(ui._bgShape->_examine.c_str(), "_EXIT", 5))
+ // It's not an exit, so include Look as an option
+ _verbCommands.push_back(FIXED(Look));
+
+ if (ui._bgShape->_aType == PERSON)
+ _verbCommands.push_back(FIXED(Talk));
+
+ // Add any extra active verbs from the object's verb list
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!ui._bgShape->_use[idx]._verb.empty() && !ui._bgShape->_use[idx]._verb.hasPrefix(" ") &&
+ (ui._bgShape->_use[idx]._target.empty() || ui._bgShape->_use[idx]._target.hasPrefix(" "))) {
+ _verbCommands.push_back(ui._bgShape->_use[idx]._verb);
+ }
+ }
+ }
+ }
+
+ // If clicked on Watson, have Journal as an option
+ if (isWatson)
+ _verbCommands.push_back(FIXED(Journal));
+
+ // Add the system commands
+ _verbCommands.push_back(FIXED(Inventory));
+ _verbCommands.push_back(FIXED(Options));
+
+ // Figure out the needed width to show the commands
+ int width = 0;
+ for (uint idx = 0; idx < _verbCommands.size(); ++idx)
+ width = MAX(width, _surface.stringWidth(_verbCommands[idx]));
+ width += _surface.widestChar() * 2 + 6;
+ int height = (_surface.fontHeight() + 7) * _verbCommands.size() + 3;
+
+ // Set the bounds
+ _bounds = Common::Rect(width, height);
+ _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
+
+ // Render the window on the internal surface
+ render();
+}
+
+void WidgetVerbs::render() {
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ ImageFile &images = *ui._interfaceImages;
+
+ // Create the drawing surface
+ _surface.create(_bounds.width(), _bounds.height());
+ _surface.fill(TRANSPARENCY);
+
+ // Draw basic background
+ makeInfoArea();
+
+ // Draw the verb commands and the lines separating them
+ for (uint idx = 0; idx < _verbCommands.size(); ++idx) {
+ _surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - _surface.stringWidth(_verbCommands[idx])) / 2,
+ (_surface.fontHeight() + 7) * idx + 5), INFO_TOP);
+
+ if (idx < (_verbCommands.size() - 1)) {
+ _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.width() - 4, INFO_TOP);
+ _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.width() - 4, INFO_MIDDLE);
+ _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.width() - 4, INFO_BOTTOM);
+
+ _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1));
+ _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width,
+ (_surface.fontHeight() + 7) * (idx + 1) - 1));
+ }
+ }
+}
+
+void WidgetVerbs::handleEvents() {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ TattooScene &scene = *(TattooScene *)_vm->_scene;
+ Talk &talk = *_vm->_talk;
+ TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
+ Common::Point mousePos = events.mousePos();
+ bool noDesc = false;
+
+ Common::String strLook = fixedText.getText(kFixedText_Look);
+ Common::String strTalk = fixedText.getText(kFixedText_Talk);
+ Common::String strJournal = fixedText.getText(kFixedText_Journal);
+
+ checkTabbingKeys(_verbCommands.size());
+
+ // Highlight verb display as necessary
+ highlightVerbControls();
+
+ // Flag if the user has started pressing the button with the cursor outsie the menu
+ if (events._firstPress && !_bounds.contains(mousePos))
+ _outsideMenu = true;
+
+ // See if they released the mouse button
+ if (events._released || events._rightReleased) {
+ // See if they want to close the menu (they clicked outside of the menu)
+ if (!_bounds.contains(mousePos)) {
+ if (_outsideMenu) {
+ if (events._rightReleased) {
+ // Change to the item (if any) that was right-clicked on, and re-draw the verb menu
+ ui._bgFound = scene.findBgShape(mousePos);
+ ui._personFound = ui._bgFound >= 1000;
+ ui._bgShape = ui._personFound || ui._bgFound == -1 ? nullptr : &scene._bgShapes[ui._bgFound];
+
+ if (ui._personFound) {
+ if (people[ui._bgFound - 1000]._description.empty() || people[ui._bgFound - 1000]._description.hasPrefix(" "))
+ noDesc = true;
+ } else if (ui._bgFound != -1) {
+ if (ui._bgShape->_description.empty() || ui._bgShape->_description.hasPrefix(" "))
+ noDesc = true;
+ } else {
+ noDesc = true;
+ }
+
+ // Call the Routine to turn on the Commands for this Object
+ load(!noDesc);
+ } else {
+ // Free the current menu graphics & erase the menu
+ banishWindow();
+
+ // See if we're in a Lab Table Room
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ }
+ }
+ } else if (_bounds.contains(mousePos) && _selector != -1) {
+ // Mouse is within the menu
+ // Erase the menu
+ banishWindow();
+
+ // See if they are activating the Look Command
+ if (!_verbCommands[_selector].compareToIgnoreCase(strLook)) {
+ ui._bgFound = ui._activeObj;
+ if (ui._activeObj >= 1000) {
+ ui._personFound = true;
+ } else {
+ ui._personFound = false;
+ ui._bgShape = &scene._bgShapes[ui._activeObj];
+ }
+
+ ui.lookAtObject();
+
+ } else if (!_verbCommands[_selector].compareToIgnoreCase(strTalk)) {
+ // Talk command is being activated
+ talk.talk(ui._activeObj);
+ ui._activeObj = -1;
+
+ } else if (!_verbCommands[_selector].compareToIgnoreCase(strJournal)) {
+ ui.doJournal();
+
+ // See if we're in a Lab Table scene
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ } else if (_selector >= ((int)_verbCommands.size() - 2)) {
+ switch (_selector - (int)_verbCommands.size() + 2) {
+ case 0:
+ // Inventory
+ ui.doInventory(2);
+ break;
+
+ case 1:
+ // Options
+ ui.doControls();
+ break;
+
+ default:
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ break;
+ }
+ } else {
+ // If they have selected anything else, process it
+ people[HOLMES].gotoStand();
+
+ if (ui._activeObj < 1000) {
+ for (int idx = 0; idx < 6; ++idx) {
+ if (!_verbCommands[_selector].compareToIgnoreCase(scene._bgShapes[ui._activeObj]._use[idx]._verb)) {
+ // See if they are Picking this object up
+ if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase("*PICKUP"))
+ ui.pickUpObject(ui._activeObj);
+ else
+ ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj);
+ }
+ }
+ } else {
+ for (int idx = 0; idx < 2; ++idx) {
+ if (!_verbCommands[_selector].compareToIgnoreCase(people[ui._activeObj - 1000]._use[idx]._verb))
+ ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj);
+ }
+ }
+
+ ui._activeObj = -1;
+ if (ui._menuMode != MESSAGE_MODE) {
+ // See if we're in a Lab Table Room
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ }
+ }
+ }
+ } else if (ui._keyState.keycode == Common::KEYCODE_ESCAPE) {
+ // User closing the menu with the ESC key
+ banishWindow();
+ ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE;
+ }
+}
+
+void WidgetVerbs::highlightVerbControls() {
+ Events &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Common::Point mousePos = events.mousePos();
+
+ // Get highlighted verb
+ _selector = -1;
+ Common::Rect bounds = _bounds;
+ bounds.grow(-3);
+ if (bounds.contains(mousePos))
+ _selector = (mousePos.y - bounds.top) / (screen.fontHeight() + 7);
+
+ // See if a new verb is being pointed at
+ if (_selector != _oldSelector) {
+ // Redraw the verb list
+ for (int idx = 0; idx < (int)_verbCommands.size(); ++idx) {
+ byte color = (idx == _selector) ? (byte)COMMAND_HIGHLIGHTED : (byte)INFO_TOP;
+ _surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - screen.stringWidth(_verbCommands[idx])) / 2,
+ (screen.fontHeight() + 7) * idx + 5), color);
+ }
+
+ _oldSelector = _selector;
+ }
+}
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/tattoo/widget_verbs.h b/engines/sherlock/tattoo/widget_verbs.h
new file mode 100644
index 0000000000..ce67842409
--- /dev/null
+++ b/engines/sherlock/tattoo/widget_verbs.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_TATTOO_WIDGET_VERBS_H
+#define SHERLOCK_TATTOO_WIDGET_VERBS_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "sherlock/tattoo/widget_base.h"
+
+namespace Sherlock {
+
+class SherlockEngine;
+
+namespace Tattoo {
+
+class WidgetVerbs: public WidgetBase {
+private:
+ int _selector, _oldSelector;
+ bool _outsideMenu;
+
+ /**
+ * Highlights the controls for the verb list
+ */
+ void highlightVerbControls();
+
+ /**
+ * Renders the window on an internal surface for later drawing on-screen
+ */
+ void render();
+public:
+ Common::StringArray _verbCommands;
+public:
+ WidgetVerbs(SherlockEngine *vm);
+ virtual ~WidgetVerbs() {}
+
+ /**
+ * Turns on the menu with all the verbs that are available for the given object
+ */
+ void load(bool objectsOn);
+
+ /**
+ * Process input for the dialog
+ */
+ virtual void handleEvents();
+};
+
+} // End of namespace Tattoo
+
+} // End of namespace Sherlock
+
+#endif
diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp
new file mode 100644
index 0000000000..9df3f1dc24
--- /dev/null
+++ b/engines/sherlock/user_interface.cpp
@@ -0,0 +1,205 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sherlock/user_interface.h"
+#include "sherlock/scalpel/scalpel.h"
+#include "sherlock/scalpel/scalpel_user_interface.h"
+#include "sherlock/tattoo/tattoo.h"
+#include "sherlock/tattoo/tattoo_user_interface.h"
+
+namespace Sherlock {
+
+UserInterface *UserInterface::init(SherlockEngine *vm) {
+ if (vm->getGameID() == GType_SerratedScalpel)
+ return new Scalpel::ScalpelUserInterface(vm);
+ else
+ return new Tattoo::TattooUserInterface(vm);
+}
+
+UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) {
+ _menuMode = STD_MODE;
+ _menuCounter = 0;
+ _infoFlag = false;
+ _windowOpen = false;
+ _endKeyActive = true;
+ _invLookFlag = 0;
+ _slideWindows = true;
+ _helpStyle = false;
+ _windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, SHERLOCK_SCREEN_HEIGHT - 1);
+ _lookScriptFlag = false;
+ _exitZone = -1;
+
+ _bgFound = _oldBgFound = -1;
+ _key = _oldKey = '\0';
+ _selector = _oldSelector = -1;
+ _temp = _oldTemp = 0;
+ _temp1 = 0;
+ _lookHelp = 0;
+}
+
+void UserInterface::checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId) {
+ Events &events = *_vm->_events;
+ FixedText &fixedText = *_vm->_fixedText;
+ People &people = *_vm->_people;
+ Scene &scene = *_vm->_scene;
+ Screen &screen = *_vm->_screen;
+ Talk &talk = *_vm->_talk;
+ Point32 pt(-1, -1);
+
+ if (action._useFlag)
+ // Automatically set the given flag
+ _vm->setFlags(action._useFlag);
+
+ if (objNum >= 1000)
+ // Ignore actions done on characters
+ return;
+
+ if (!action._cAnimSpeed) {
+ // Invalid action, to print error message
+ _infoFlag = true;
+ clearInfo();
+ Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, action._cAnimNum);
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str());
+ _infoFlag = true;
+
+ // Set how long to show the message
+ _menuCounter = 30;
+ } else {
+ Object &obj = scene._bgShapes[objNum];
+
+ int cAnimNum;
+ if (action._cAnimNum == 0)
+ // Really a 10
+ cAnimNum = 9;
+ else
+ cAnimNum = action._cAnimNum - 1;
+
+ int dir = -1;
+ if (action._cAnimNum != 99) {
+ CAnim &anim = scene._cAnim[cAnimNum];
+
+ if (action._cAnimNum != 99) {
+ if (action._cAnimSpeed & REVERSE_DIRECTION) {
+ pt = anim._teleport[0];
+ dir = anim._teleport[0]._facing;
+ } else {
+ pt = anim._goto[0];
+ dir = anim._goto[0]._facing;
+ }
+ }
+ } else {
+ pt = Point32(-1, -1);
+ dir = -1;
+ }
+
+ // Has a value, so do action
+ // Show wait cursor whilst walking to object and doing action
+ events.setCursor(WAIT);
+ bool printed = false;
+
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
+ && toupper(action._names[nameIdx][1]) == 'W') {
+ if (obj.checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+ }
+
+ bool doCAnim = true;
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) {
+ char ch = toupper(action._names[nameIdx][1]);
+
+ if (ch == 'T' || ch == 'B') {
+ printed = true;
+ if (pt.x != -1)
+ // Holmes needs to walk to object before the action is done
+ people[HOLMES].walkToCoords(pt, dir);
+
+ if (!talk._talkToAbort) {
+ // Ensure Holmes is on the exact intended location
+ people[HOLMES]._position = pt;
+ people[HOLMES]._sequenceNumber = dir;
+ people[HOLMES].gotoStand();
+
+ talk.talkTo(action._names[nameIdx].c_str() + 2);
+ if (ch == 'T')
+ doCAnim = false;
+ }
+ }
+ }
+ }
+
+ if (doCAnim && !talk._talkToAbort) {
+ if (pt.x != -1)
+ // Holmes needs to walk to object before the action is done
+ people[HOLMES].walkToCoords(pt, dir);
+ }
+
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) {
+ if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2
+ && toupper(action._names[nameIdx][1]) == 'F') {
+ if (obj.checkNameForCodes(action._names[nameIdx].c_str() + 2, fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+ }
+
+ if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99)
+ scene.startCAnim(cAnimNum, action._cAnimSpeed);
+
+ if (!talk._talkToAbort) {
+ for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) {
+ if (obj.checkNameForCodes(action._names[nameIdx], fixedTextActionId)) {
+ if (!talk._talkToAbort)
+ printed = true;
+ }
+ }
+
+ // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set
+ if (IS_SERRATED_SCALPEL && scene._goToScene != 1 && !printed && !talk._talkToAbort) {
+ _infoFlag = true;
+ clearInfo();
+ screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Done...");
+
+ // Set how long to show the message
+ _menuCounter = 30;
+ }
+ }
+ }
+
+ // Reset cursor back to arrow
+ events.setCursor(ARROW);
+}
+
+void UserInterface::reset() {
+ _bgFound = _oldBgFound = -1;
+ _oldKey = -1;
+ _oldTemp = _temp = -1;
+ _exitZone = -1;
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/user_interface.h b/engines/sherlock/user_interface.h
new file mode 100644
index 0000000000..9af5ad5ae6
--- /dev/null
+++ b/engines/sherlock/user_interface.h
@@ -0,0 +1,138 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SHERLOCK_UI_H
+#define SHERLOCK_UI_H
+
+#include "common/scummsys.h"
+#include "common/events.h"
+#include "sherlock/surface.h"
+#include "sherlock/objects.h"
+#include "sherlock/resources.h"
+#include "sherlock/fixed_text.h"
+
+namespace Sherlock {
+
+#define CONTROLS_Y 138
+#define CONTROLS_Y1 151
+
+enum MenuMode {
+ STD_MODE = 0,
+ LOOK_MODE = 1,
+ MOVE_MODE = 2,
+ TALK_MODE = 3,
+ PICKUP_MODE = 4,
+ OPEN_MODE = 5,
+ CLOSE_MODE = 6,
+ INV_MODE = 7,
+ USE_MODE = 8,
+ GIVE_MODE = 9,
+ JOURNAL_MODE = 10,
+ FILES_MODE = 11,
+ SETUP_MODE = 12,
+
+ // Rose Tattoo specific
+ LAB_MODE = 20,
+ MESSAGE_MODE = 21,
+ VERB_MODE = 22
+};
+
+class UserInterface {
+protected:
+ SherlockEngine *_vm;
+
+ UserInterface(SherlockEngine *vm);
+public:
+ MenuMode _menuMode;
+ int _menuCounter;
+ bool _infoFlag;
+ bool _windowOpen;
+ bool _endKeyActive;
+ int _invLookFlag;
+ bool _slideWindows;
+ bool _helpStyle;
+ Common::Rect _windowBounds;
+ bool _lookScriptFlag;
+ int _bgFound, _oldBgFound;
+ int _exitZone;
+
+ // TODO: Not so sure these should be in the base class. May want to refactor them to SherlockEngine, or refactor
+ // various Scalpel dialogs to keep their own private state of key/selections
+ signed char _key, _oldKey;
+ int _selector, _oldSelector;
+ int _temp, _oldTemp;
+ int _temp1;
+ int _lookHelp;
+public:
+ static UserInterface *init(SherlockEngine *vm);
+ virtual ~UserInterface() {}
+
+ /**
+ * Called for OPEN, CLOSE, and MOVE actions are being done
+ */
+ void checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid);
+public:
+ /**
+ * Resets the user interface
+ */
+ virtual void reset();
+
+ /**
+ * Draw the user interface onto the screen's back buffers
+ */
+ virtual void drawInterface(int bufferNum = 3) {}
+
+ /**
+ * Main input handler for the user interface
+ */
+ virtual void handleInput() {}
+
+ /**
+ * Displays a passed window by gradually scrolling it vertically on-screen
+ */
+ virtual void summonWindow(const Surface &bgSurface, bool slideUp = true) {}
+
+ /**
+ * Slide the window stored in the back buffer onto the screen
+ */
+ virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y) {}
+
+ /**
+ * Close a currently open window
+ * @param flag 0 = slide old window down, 1 = slide prior UI back up
+ */
+ virtual void banishWindow(bool slideUp = true) {}
+
+ /**
+ * Clears the info line of the screen
+ */
+ virtual void clearInfo() {}
+
+ /**
+ * Clear any active text window
+ */
+ virtual void clearWindow() {}
+};
+
+} // End of namespace Sherlock
+
+#endif