/* 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_fixed_text.h" #include "sherlock/tattoo/tattoo_journal.h" #include "sherlock/tattoo/tattoo_scene.h" #include "sherlock/tattoo/tattoo.h" namespace Sherlock { namespace Tattoo { bool WidgetList::contains(const WidgetBase *item) const { for (const_iterator i = begin(); i != end(); ++i) { if ((*i) == item) return true; } return false; } /*-------------------------------------------------------------------------*/ TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm), _inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm), _verbsWidget(vm), _creditsWidget(vm), _optionsWidget(vm), _quitWidget(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; _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; delete _mask; delete _mask1; } 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; _lookPos = mousePos; _menuMode = LOOK_MODE; if (_personFound) { desc = people[_bgFound - 1000]._examine; } else { // Check if there is a Look animation if (_bgShape->_lookcAnim != 0) { int 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 speech 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; byte lookupTable[PALETTE_COUNT], lookupTable1[PALETTE_COUNT]; Common::copy(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], &lookupTable[0]); Common::copy(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], &lookupTable1[0]); _menuMode = JOURNAL_MODE; journal.show(); _menuMode = STD_MODE; _windowOpen = false; _key = -1; // Restore the the old screen palette and greyscale lookup table screen.clear(); screen.setPalette(screen._cMap); Common::copy(&lookupTable[0], &lookupTable[PALETTE_COUNT], &_lookupTable[0]); Common::copy(&lookupTable1[0], &lookupTable1[PALETTE_COUNT], &_lookupTable1[0]); // Restore the scene screen._backBuffer1.blitFrom(screen._backBuffer2); scene.updateBackground(); screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, 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(); _fixedWidgets.clear(); } void TattooUserInterface::handleInput() { TattooEngine &vm = *(TattooEngine *)_vm; Events &events = *_vm->_events; TattooScene &scene = *(TattooScene *)_vm->_scene; Common::Point mousePos = events.mousePos(); _keyState.keycode = Common::KEYCODE_INVALID; // Check for credits starting if (_vm->readFlags(3000) && !_creditsWidget.active()) _creditsWidget.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_ESCAPE && vm._runningProlog && !_lockoutTimer) { vm.setFlags(-76); vm.setFlags(396); scene._goToScene = STARTING_GAME_SCENE; } else if (_menuMode == STD_MODE) { if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode) { events.toggleSpeed(); } else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) { // Beging used for testing that Look dialogs work lookAtObject(); } } } 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(); else if (!_fixedWidgets.empty()) _fixedWidgets.back()->handleEvents(); // Handle input depending on what mode we're in switch (_menuMode) { case STD_MODE: doStandardControl(); break; case LOOK_MODE: doLookControl(); break; default: break; } } void TattooUserInterface::drawInterface(int bufferNum) { Screen &screen = *_vm->_screen; // Draw any active on-screen widgets for (Common::List::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i) (*i)->draw(); for (Common::List::iterator i = _widgets.begin(); i != _widgets.end(); ++i) (*i)->draw(); // Handle drawing credits // TODO: See if credits are only shown on a single screen. If so, _fixedWidgets could be used if (_creditsWidget.active()) _creditsWidget.drawCredits(); // Bring the widgets to the screen if (_mask != nullptr) screen._flushScreen = true; } 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::iterator i = _widgets.begin(); i != _widgets.end(); ++i) (*i)->erase(); for (Common::List::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.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 its 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; } // Reset the default look position to the center of the new screen area _lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); } void TattooUserInterface::doStandardControl() { TattooEngine &vm = *(TattooEngine *)_vm; Events &events = *_vm->_events; People &people = *_vm->_people; SaveManager &saves = *_vm->_saves; TattooScene &scene = *(TattooScene *)_vm->_scene; Talk &talk = *_vm->_talk; Common::Point mousePos = events.mousePos(); // Don't do any input processing whilst the prolog is running if (vm._runningProlog) return; // When the end credits are active, any press will open the ScummVM global main menu if (_creditsWidget.active()) { if (_keyState.keycode || events._released || events._rightReleased) { vm._canLoadSave = true; vm.openMainMenuDialog(); vm._canLoadSave = false; } return; } // Display the names of any Objects the cursor is pointing at displayObjectNames(); switch (_keyState.keycode) { case Common::KEYCODE_F5: // Save game events.warpMouse(); saveGame(); return; case Common::KEYCODE_F7: // Load game events.warpMouse(); loadGame(); 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 events.warpMouse(); _optionsWidget.load(); return; case Common::KEYCODE_F10: // Quit menu freeMenu(); events.warpMouse(); 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(); bool noDesc = false; 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(); saves.createThumbnail(); _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.initTalk(_bgFound); _activeObj = -1; } else if (!noDesc) { // Either call the code to Look at its 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; // See if a mouse button was released or a key pressed to close the active look dialog if (events._released || events._rightReleased || _keyState.keycode) { // 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::displayObjectNames() { Events &events = *_vm->_events; Scene &scene = *_vm->_scene; Common::Point mousePos = events.mousePos(); _arrowZone = -1; if (_bgFound == -1 || scene._currentScene == OVERHEAD_MAP2) { 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::doInventory(int mode) { People &people = *_vm->_people; people[HOLMES].gotoStand(); _inventoryWidget.load(mode); _inventoryWidget.summonWindow(); _menuMode = INV_MODE; } void TattooUserInterface::doControls() { _optionsWidget.load(); } void TattooUserInterface::pickUpObject(int objNum) { Inventory &inv = *_vm->_inventory; Scene &scene = *_vm->_scene; Talk &talk = *_vm->_talk; Object &obj = scene._bgShapes[objNum]; bool printed = false; int verbField = -1; // Find which Verb field to use for pick up data for (int idx = 0; idx < 6; ++idx) { if (!scumm_stricmp(obj._use[idx]._target.c_str(), "*PICKUP")) verbField = idx; } if (verbField != -1) { if (obj._use[verbField]._cAnimNum) scene.startCAnim(obj._use[verbField]._cAnimNum - 1); } if (!talk._talkToAbort) { if (obj._type == NO_SHAPE) obj._type = INVALID; else // Erase shape obj._type = REMOVE; } else { return; } if (verbField != -1) { for (int idx = 0; idx < 4 && !talk._talkToAbort; ++idx) { if (obj.checkNameForCodes(obj._use[verbField]._names[idx])) { if (!talk._talkToAbort) printed = true; } } } if (talk._talkToAbort) return; // Add the item to the player's inventory inv.putItemInInventory(obj); if (!printed) { Common::String desc = obj._description; desc.setChar(tolower(desc[0]), 0); putMessage("%s %s", FIXED(PickedUp), desc.c_str()); } if (_menuMode != TALK_MODE && _menuMode != MESSAGE_MODE) { _menuMode = STD_MODE; _keyState.keycode = Common::KEYCODE_INVALID; } } void TattooUserInterface::doQuitMenu() { _quitWidget.show(); } 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] * 30 + cMap[idx * 3 + 1] * 59 + cMap[idx * 3 + 2] * 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 = 0xff; int cd = 99999; 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() { 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) { // Since a mask is active, restore the screen from the secondary back buffer prior to applying the mask screen._backBuffer1.blitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0, screen._currentScroll.x + 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 (_creditsWidget.active()) _creditsWidget.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; int xp = mode ? _maskOffset.x : 0; if (_mask != nullptr) { switch (scene._currentScene) { case 7: maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110)); maskArea(*_mask, Common::Point(_maskOffset.x, 110)); maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110)); break; case 8: maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180)); maskArea(*_mask, Common::Point(_maskOffset.x, 180)); maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180)); if (!_vm->readFlags(880)) maskArea(*_mask1, Common::Point(940, 300)); break; case 18: maskArea(*_mask, Common::Point(xp, 203)); if (!_vm->readFlags(189)) maskArea(*_mask1, Common::Point(124 + xp, 239)); break; case 53: maskArea(*_mask, Common::Point(_maskOffset.x, 110)); if (mode) maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110)); break; case 68: maskArea(*_mask, Common::Point(xp, 203)); maskArea(*_mask1, Common::Point(124 + xp, 239)); break; } } } void TattooUserInterface::maskArea(Common::SeekableReadStream &mask, const Common::Point &pt) { Screen &screen = *_vm->_screen; Surface &bb1 = screen._backBuffer1; mask.seek(0); int xSize = mask.readUint16LE(); int ySize = mask.readUint16LE(); int pixel, len, xp, yp; for (yp = 0; yp < ySize; ++yp) { byte *ptr = bb1.getBasePtr(pt.x, pt.y + yp); for (xp = 0; xp < xSize;) { // The mask data consists of pairs of pixel/lengths, where all non-zero pixels means that the // given pixel on the back buffer is darkened (the mask pixel value isn't otherwise used) pixel = mask.readByte(); len = mask.readByte(); for (; len > 0; --len, ++xp, ++ptr) { if (pixel && (pt.x + xp) >= screen._currentScroll.x && (pt.x + xp) < (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) { *ptr = _lookupTable1[*ptr]; } } } assert(xp == xSize); } } 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) { if (!_widgets.empty()) _widgets.back()->banishWindow(); } void TattooUserInterface::freeMenu() { for (Common::List::iterator i = _widgets.begin(); i != _widgets.end(); ++i) (*i)->erase(); _widgets.clear(); } void TattooUserInterface::clearWindow() { banishWindow(); } void TattooUserInterface::loadGame() { WidgetFiles &files = *(WidgetFiles *)_vm->_saves; files.show(SAVEMODE_LOAD); } void TattooUserInterface::saveGame() { WidgetFiles &files = *(WidgetFiles *)_vm->_saves; files.show(SAVEMODE_SAVE); } void TattooUserInterface::addFixedWidget(WidgetBase *widget) { _fixedWidgets.push_back(widget); widget->summonWindow(); } } // End of namespace Tattoo } // End of namespace Sherlock