diff options
Diffstat (limited to 'engines/sherlock/tattoo')
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 |