Files
scummvm-cursorfix/engines/nancy/action/puzzle/peepholepuzzle.cpp
2026-02-02 04:50:13 +01:00

366 lines
10 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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