aboutsummaryrefslogtreecommitdiff
path: root/engines/sherlock/tattoo
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sherlock/tattoo')
-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
38 files changed, 12121 insertions, 0 deletions
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