/* 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 3 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, see . * */ #include "engines/nancy/graphics.h" #include "engines/nancy/nancy.h" #include "engines/nancy/sound.h" #include "engines/nancy/input.h" #include "engines/nancy/util.h" #include "engines/nancy/resource.h" #include "engines/nancy/action/puzzle/peepholepuzzle.h" #include "engines/nancy/state/scene.h" namespace Nancy { namespace Action { void PeepholePuzzle::init() { Common::Rect screenBounds = NancySceneState.getViewport().getBounds(); _drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat()); moveTo(screenBounds); Common::Rect innerImageContentBounds; g_nancy->_resource->loadImage(_innerImageName, _innerImage, Common::String(), &innerImageContentBounds); if (!innerImageContentBounds.isEmpty()) { // When using Autotext, make sure scrolling stops with the end of the text content. // This was implemented in nancy7, but it's better to have it on for nancy6 as well. _innerBounds.clip(innerImageContentBounds); } if (_buttonsImageName.empty()) { // Empty image name for buttons, use other image as source _buttonsImage.create(_innerImage, _innerImage.getBounds()); } else { g_nancy->_resource->loadImage(_buttonsImageName, _buttonsImage); } _currentSrc = _startSrc; setTransparent(_transparency == kPlayOverlayTransparent); _drawSurface.clear(_drawSurface.getTransparentColor()); setVisible(true); drawInner(); checkButtons(); } void PeepholePuzzle::readData(Common::SeekableReadStream &stream) { readFilename(stream, _innerImageName); readFilename(stream, _buttonsImageName); _transparency = stream.readUint16LE(); readRect(stream, _innerBounds); readRect(stream, _startSrc); readRect(stream, _dest); readRectArray(stream, _buttonDests, 4); readRectArray(stream, _buttonSrcs, 4); readRectArray(stream, _buttonDisabledSrcs, 4); _pixelsToScroll = stream.readByte(); _exitScene.readData(stream); readRect(stream, _exitHotspot); } void PeepholePuzzle::execute() { switch (_state) { case kBegin: init(); registerGraphics(); _state = kRun; // fall through case kRun: break; case kActionTrigger: finishExecution(); } } void PeepholePuzzle::handleInput(NancyInput &input) { if (_state != kRun) { return; } bool justReleased = false; if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) { g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor); if (input.input & NancyInput::kLeftMouseButtonUp) { _state = kActionTrigger; } } if (_pressedButton != -1) { if (input.input & NancyInput::kLeftMouseButtonHeld) { // Player is still holding the left button, check if mouse has moved outside bounds if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos)) { // NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons if (!_disabledButtons[_pressedButton]) { // Do not show hover cursor on disabled button g_nancy->_cursor->setCursorType(CursorManager::kHotspot); } if (_pressStart == 0) { // Mouse was out of bounds but still held, and is now back in bounds, continue moving _pressStart = g_nancy->getTotalPlayTime(); } } else { // Mouse is not in bounds, pause moving _pressStart = 0; justReleased = true; } } else { // Player released mouse button // Avoid single frame with non-highlighted cursor // NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos) && !_disabledButtons[_pressedButton]) { g_nancy->_cursor->setCursorType(CursorManager::kHotspot); } _pressedButton = -1; _pressStart = 0; justReleased = true; } } else { // Mouse is not currently pressing button, check all buttons for (uint i = 0; i < 4; ++i) { // NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons if (!_disabledButtons[i]) { if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[i]).contains(input.mousePos)) { g_nancy->_cursor->setCursorType(CursorManager::kHotspot); if (input.input & NancyInput::kLeftMouseButtonDown) { if (_pressedButton == -1) { // Just pressed _pressedButton = i; _pressStart = g_nancy->getTotalPlayTime(); } } } } } } // Perform movement if (_pressedButton != -1 && _pressStart != 0) { uint32 curTime = g_nancy->getTotalPlayTime(); int16 pixelsToMove = 0; if (curTime - _pressStart >= 1000u / _pixelsToScroll) { pixelsToMove = (curTime - _pressStart) / (1000 / _pixelsToScroll); } switch (_pressedButton) { case 0 : // Up _currentSrc.translate(0, -pixelsToMove); if (_currentSrc.top < _innerBounds.top) { _currentSrc.translate(0, _innerBounds.top - _currentSrc.top); } break; case 1 : // Down _currentSrc.translate(0, pixelsToMove); if (_currentSrc.bottom > _innerBounds.bottom) { _currentSrc.translate(0, _innerBounds.bottom - _currentSrc.bottom); } break; case 2 : // Left _currentSrc.translate(-pixelsToMove, 0); if (_currentSrc.left < _innerBounds.left) { _currentSrc.translate(_innerBounds.left - _currentSrc.left, 0); } break; case 3: // Right _currentSrc.translate(pixelsToMove, 0); if (_currentSrc.right > _innerBounds.right) { _currentSrc.translate(_innerBounds.right - _currentSrc.right, 0); } break; } _pressStart = curTime; checkButtons(); drawInner(); } else if (justReleased) { checkButtons(); } } void PeepholePuzzle::drawInner() { _drawSurface.blitFrom(_innerImage, _currentSrc, _dest); _needsRedraw = true; } void PeepholePuzzle::checkButtons() { for (int i = 0; i < 4; ++i) { int16 *srcCoord = nullptr; int16 *innerCoord = nullptr; switch(i) { case 0 : srcCoord = &_currentSrc.top; innerCoord = &_innerBounds.top; break; case 1 : srcCoord = &_currentSrc.bottom; innerCoord = &_innerBounds.bottom; break; case 2 : srcCoord = &_currentSrc.left; innerCoord = &_innerBounds.left; break; case 3 : srcCoord = &_currentSrc.right; innerCoord = &_innerBounds.right; break; } if (!_buttonDests[i].isEmpty()) { if (*srcCoord == *innerCoord) { if (_disabledButtons[i] == false) { _disabledButtons[i] = true; if (!_buttonDisabledSrcs[i].isEmpty()) { _drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[i], _buttonDests[i]); _needsRedraw = true; } } } else { _disabledButtons[i] = false; if (i == _pressedButton && _pressStart) { _drawSurface.blitFrom(_buttonsImage, _buttonSrcs[i], _buttonDests[i]); _needsRedraw = true; } else { _drawSurface.fillRect(_buttonDests[i], _drawSurface.getTransparentColor()); _needsRedraw = true; } } } else { _disabledButtons[i] = true; } } // Ensure that contents that do not overflow can't be scrolled if (_innerBounds.height() <= _dest.height()) { _disabledButtons[0] = _disabledButtons[1] = true; if (!_buttonDisabledSrcs[0].isEmpty()) { _drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[0], _buttonDests[0]); } if (!_buttonDisabledSrcs[1].isEmpty()) { _drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[1], _buttonDests[1]); } } if (_innerBounds.width() <= _dest.width()) { _disabledButtons[2] = _disabledButtons[3] = true; if (!_buttonDisabledSrcs[2].isEmpty()) { _drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[2], _buttonDests[2]); } if (!_buttonDisabledSrcs[3].isEmpty()) { _drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[3], _buttonDests[3]); } } } void TextScroll::init() { Autotext::execute(); _isDone = false; // Set to true by Autotext // Supply the correct names to the resource manager if (_surfaceID < 3) { _innerImageName = Common::String::format("USE_AUTOTEXT%u", _surfaceID + 1); } else { _innerImageName = Common::String::format("USE_AUTOLIST%u", _surfaceID - 2); } // Make sure the initial bounds match the surface's _innerBounds = _fullSurface.getBounds(); PeepholePuzzle::init(); } void TextScroll::readData(Common::SeekableReadStream &stream) { Autotext::readData(stream); PeepholePuzzle::_transparency = Autotext::_transparency; } void TextScroll::readExtraData(Common::SeekableReadStream &stream) { _order = stream.readUint16LE(); _shouldDrawMarks = stream.readByte(); readFilename(stream, _buttonsImageName); readRect(stream, _startSrc); readRect(stream, _dest); readRectArray(stream, _buttonDests, 4); readRectArray(stream, _buttonSrcs, 4); readRectArray(stream, _buttonDisabledSrcs, 4); _pixelsToScroll = stream.readByte(); if (!_isEntryList) { Autotext::readExtraData(stream); } } void TextScroll::handleInput(NancyInput &input) { PeepholePuzzle::handleInput(input); // Finally, check hotspots for (uint i = 0; i < _hotspots.size(); ++i) { Common::Rect hotspot = _hotspots[i]; hotspot.translate(_dest.left, _dest.top); Common::Point innerOffset = _drawSurface.getOffsetFromOwner(); hotspot.translate(-innerOffset.x, -innerOffset.y); hotspot.clip(_dest); if (!hotspot.isEmpty() && NancySceneState.getViewport().convertViewportToScreen(hotspot).contains(input.mousePos)) { g_nancy->_cursor->setCursorType(CursorManager::kHotspot); if (input.input & NancyInput::kLeftMouseButtonUp) { // Clicked on a hotspot, change to corresponding scene SceneChangeDescription sceneChange; sceneChange.sceneID = _hotspotScenes[i]; sceneChange.continueSceneSound = kContinueSceneSound; NancySceneState.changeScene(sceneChange); } break; } } } } // End of namespace Action } // End of namespace Nancy