467 lines
14 KiB
C++
467 lines
14 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/util.h"
|
|
#include "engines/nancy/nancy.h"
|
|
#include "engines/nancy/resource.h"
|
|
#include "engines/nancy/graphics.h"
|
|
#include "engines/nancy/sound.h"
|
|
#include "engines/nancy/input.h"
|
|
#include "engines/nancy/cursor.h"
|
|
#include "engines/nancy/puzzledata.h"
|
|
|
|
#include "engines/nancy/state/scene.h"
|
|
#include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
|
|
|
|
#include "graphics/transform_struct.h"
|
|
|
|
namespace Nancy {
|
|
namespace Action {
|
|
|
|
void RippedLetterPuzzle::init() {
|
|
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
|
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
|
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
|
setTransparent(true);
|
|
setVisible(true);
|
|
moveTo(screenBounds);
|
|
|
|
g_nancy->_resource->loadImage(_imageName, _image);
|
|
|
|
if (_useCustomPickUpTile) {
|
|
_pickedUpPiece._drawSurface.create(_image, _customPickUpTileSrc);
|
|
} else {
|
|
_pickedUpPiece._drawSurface.create(_destRects[0].width(), _destRects[0].height(), g_nancy->_graphics->getInputPixelFormat());
|
|
}
|
|
|
|
_pickedUpPiece.setVisible(false);
|
|
}
|
|
|
|
void RippedLetterPuzzle::registerGraphics() {
|
|
_pickedUpPiece.registerGraphics();
|
|
RenderObject::registerGraphics();
|
|
}
|
|
|
|
void RippedLetterPuzzle::readData(Common::SeekableReadStream &stream) {
|
|
_puzzleState = (RippedLetterPuzzleData *)NancySceneState.getPuzzleData(RippedLetterPuzzleData::getTag());
|
|
assert(_puzzleState);
|
|
|
|
readFilename(stream, _imageName);
|
|
|
|
byte maxWidth = 6;
|
|
byte maxHeight = g_nancy->getGameType() <= kGameTypeNancy6 ? 4 : 5;
|
|
byte width = maxWidth;
|
|
byte height = maxHeight;
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy5) {
|
|
width = stream.readByte();
|
|
height = stream.readByte();
|
|
}
|
|
|
|
// All the checks for whether width is greater than maxWidth are
|
|
// to account for nancy9 scene 2428, where the dimensions are 15x1
|
|
|
|
for (uint i = 0; i < height; ++i) {
|
|
readRectArray(stream, _srcRects, width, maxWidth);
|
|
}
|
|
stream.skip((maxWidth >= width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * 16);
|
|
|
|
for (uint i = 0; i < height; ++i) {
|
|
readRectArray(stream, _destRects, width, maxWidth);
|
|
}
|
|
stream.skip((maxWidth >= width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * 16);
|
|
|
|
readRect(stream, _rotateHotspot);
|
|
readRect(stream, _takeHotspot);
|
|
readRect(stream, _dropHotspot);
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
|
_rotationType = (RotationType)stream.readUint16LE();
|
|
}
|
|
|
|
uint elemSize = g_nancy->getGameType() <= kGameTypeNancy8 ? 1 : 2;
|
|
|
|
_initOrder.resize(width * height);
|
|
assert(width * height <= 24); // If this gets hit we need to increase the sizes in RippedLetterPuzzleData
|
|
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_initOrder[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
|
|
_initRotations.resize(width * height);
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_initRotations[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
|
uint16 numDoubledElements = stream.readUint16LE();
|
|
_doubles.resize(numDoubledElements);
|
|
uint i = 0;
|
|
for (uint j = 0; j < 20; ++j) {
|
|
int16 id = stream.readSint16LE();
|
|
if (id == -1) {
|
|
++i;
|
|
} else {
|
|
_doubles[i].push_back(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
_solveOrder.resize(width * height);
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_solveOrder[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
|
|
_solveRotations.resize(width * height);
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_solveRotations[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
|
_useAltSolution = stream.readByte();
|
|
|
|
_solveOrderAlt.resize(width * height);
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_solveOrderAlt[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
|
|
_solveRotationsAlt.resize(width * height);
|
|
for (uint i = 0; i < height; ++i) {
|
|
for (uint j = 0; j < width; ++j) {
|
|
_solveRotationsAlt[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
|
}
|
|
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
|
}
|
|
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
|
}
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
|
_useCustomPickUpTile = stream.readByte();
|
|
readRect(stream, _customPickUpTileSrc);
|
|
}
|
|
|
|
_takeSound.readNormal(stream);
|
|
_dropSound.readNormal(stream);
|
|
_rotateSound.readNormal(stream);
|
|
|
|
_solveExitScene.readData(stream);
|
|
_solveSound.readNormal(stream);
|
|
|
|
_exitScene.readData(stream);
|
|
readRect(stream, _exitHotspot);
|
|
|
|
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
|
_customCursorID = stream.readSint16LE();
|
|
}
|
|
}
|
|
|
|
void RippedLetterPuzzle::execute() {
|
|
switch (_state) {
|
|
case kBegin:
|
|
_puzzleState = (RippedLetterPuzzleData *)NancySceneState.getPuzzleData(RippedLetterPuzzleData::getTag());
|
|
assert(_puzzleState);
|
|
|
|
init();
|
|
registerGraphics();
|
|
|
|
NancySceneState.setNoHeldItem();
|
|
|
|
if (!_puzzleState->playerHasTriedPuzzle) {
|
|
_puzzleState->order = _initOrder;
|
|
_puzzleState->rotations = _initRotations;
|
|
_puzzleState->playerHasTriedPuzzle = true;
|
|
} else if (_puzzleState->_pickedUpPieceID != -1) {
|
|
// Puzzle was left while still holding a piece (e.g. by clicking a scene item).
|
|
// Make sure we put the held piece back in its place
|
|
_puzzleState->order[_puzzleState->_pickedUpPieceLastPos] = _puzzleState->_pickedUpPieceID;
|
|
_puzzleState->rotations[_puzzleState->_pickedUpPieceLastPos] = _puzzleState->_pickedUpPieceRot;
|
|
_puzzleState->_pickedUpPieceID = -1;
|
|
_puzzleState->_pickedUpPieceLastPos = -1;
|
|
_puzzleState->_pickedUpPieceRot = 0;
|
|
}
|
|
|
|
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
|
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
|
}
|
|
|
|
g_nancy->_sound->loadSound(_takeSound);
|
|
g_nancy->_sound->loadSound(_dropSound);
|
|
g_nancy->_sound->loadSound(_rotateSound);
|
|
|
|
_state = kRun;
|
|
// fall through
|
|
case kRun:
|
|
switch (_solveState) {
|
|
case kNotSolved :
|
|
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
|
if (_puzzleState->rotations[i] != _solveRotations[i] || !checkOrder(false)) {
|
|
if (_useAltSolution) {
|
|
if (!checkOrder(true)) {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_nancy->_sound->loadSound(_solveSound);
|
|
g_nancy->_sound->playSound(_solveSound);
|
|
_solveState = kWaitForSound;
|
|
break;
|
|
case kWaitForSound :
|
|
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
|
g_nancy->_sound->stopSound(_solveSound);
|
|
_state = kActionTrigger;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case kActionTrigger :
|
|
switch (_solveState) {
|
|
case kNotSolved:
|
|
_exitScene.execute();
|
|
break;
|
|
case kWaitForSound:
|
|
if (_solveExitScene._sceneChange.sceneID == NancySceneState.getSceneInfo().sceneID) {
|
|
// nancy9 scene 2484 is auto-solved for you, but has a valid scene change back to itself
|
|
return;
|
|
}
|
|
_solveExitScene.execute();
|
|
_puzzleState->playerHasTriedPuzzle = false;
|
|
break;
|
|
}
|
|
|
|
g_nancy->_sound->stopSound(_takeSound);
|
|
g_nancy->_sound->stopSound(_dropSound);
|
|
g_nancy->_sound->stopSound(_rotateSound);
|
|
finishExecution();
|
|
}
|
|
}
|
|
|
|
void RippedLetterPuzzle::handleInput(NancyInput &input) {
|
|
if (_state == kBegin) {
|
|
return;
|
|
}
|
|
|
|
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
|
Common::Rect screenHotspot = NancySceneState.getViewport().convertViewportToScreen(_destRects[i]);
|
|
if (screenHotspot.contains(input.mousePos)) {
|
|
Common::Rect insideRect;
|
|
if (_puzzleState->_pickedUpPieceID == -1) {
|
|
// No piece picked up
|
|
|
|
// Check if the mouse is inside the rotation hotspot
|
|
insideRect = _rotateHotspot;
|
|
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
|
|
|
if (_rotationType != kRotationNone && insideRect.contains(input.mousePos)) {
|
|
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
|
|
|
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
|
// Player has clicked, rotate the piece
|
|
int inc = (_rotationType == kRotation90 ? 1 : 2);
|
|
if ((_puzzleState->rotations[i] += inc) > 3) {
|
|
_puzzleState->rotations[i] -= 4;
|
|
}
|
|
|
|
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
|
g_nancy->_sound->playSound(_rotateSound);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Check if the mouse is inside the pickup hotspot
|
|
insideRect = _takeHotspot;
|
|
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
|
|
|
if (insideRect.contains(input.mousePos)) {
|
|
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
|
|
|
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
|
// Player has clicked, take the piece
|
|
|
|
// First, copy the graphic from the full drawSurface...
|
|
if (!_useCustomPickUpTile) {
|
|
_pickedUpPiece._drawSurface.clear(g_nancy->_graphics->getTransColor());
|
|
_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
|
|
}
|
|
|
|
_pickedUpPiece.setVisible(true);
|
|
_pickedUpPiece.setTransparent(true);
|
|
_pickedUpPiece.pickUp();
|
|
|
|
// ...then change the data...
|
|
_puzzleState->_pickedUpPieceID = _puzzleState->order[i];
|
|
_puzzleState->_pickedUpPieceRot = _puzzleState->rotations[i];
|
|
_puzzleState->order[i] = -1;
|
|
_puzzleState->_pickedUpPieceLastPos = i;
|
|
|
|
// ...then clear the piece from the drawSurface
|
|
drawPiece(i, 0);
|
|
|
|
g_nancy->_sound->playSound(_takeSound);
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else {
|
|
// Currently carrying a piece
|
|
|
|
// Check if the mouse is inside the drop hotspot
|
|
insideRect = _dropHotspot;
|
|
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
|
|
|
if (insideRect.contains(input.mousePos)) {
|
|
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
|
|
|
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
|
// Player has clicked, drop the piece and pick up a new one
|
|
|
|
// Check if we should pick up a new piece
|
|
if (_puzzleState->order[i] == -1) {
|
|
// No, hide the picked up piece graphic
|
|
_pickedUpPiece.setVisible(false);
|
|
_puzzleState->_pickedUpPieceLastPos = -1;
|
|
} else {
|
|
// Yes, change the picked piece graphic
|
|
if (!_useCustomPickUpTile) {
|
|
_pickedUpPiece._drawSurface.clear(g_nancy->_graphics->getTransColor());
|
|
_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
|
|
}
|
|
|
|
_pickedUpPiece.setVisible(true);
|
|
_pickedUpPiece.setTransparent(true);
|
|
}
|
|
|
|
SWAP<int8>(_puzzleState->order[i], _puzzleState->_pickedUpPieceID);
|
|
SWAP<byte>(_puzzleState->rotations[i], _puzzleState->_pickedUpPieceRot);
|
|
|
|
// Draw the newly placed piece
|
|
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
|
|
|
g_nancy->_sound->playSound(_dropSound);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_pickedUpPiece.handleInput(input);
|
|
|
|
if (_puzzleState->_pickedUpPieceID == -1) {
|
|
// No piece picked up, check the exit hotspot
|
|
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
|
g_nancy->_cursor->setCursorType(_customCursorID != -1 ? (CursorManager::CursorType)_customCursorID : g_nancy->_cursor->_puzzleExitCursor);
|
|
|
|
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
|
// Player has clicked, exit
|
|
_state = kActionTrigger;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RippedLetterPuzzle::drawPiece(const uint pos, const byte rotation, const int pieceID) {
|
|
// Clear the selected position
|
|
_drawSurface.fillRect(_destRects[pos], _drawSurface.getTransparentColor());
|
|
_needsRedraw = true;
|
|
|
|
// No piece, just clear
|
|
if (pieceID == -1) {
|
|
return;
|
|
}
|
|
|
|
// Create temporary ManagedSurfaces and call the custom rotation function
|
|
Graphics::ManagedSurface srcSurf(_image, _srcRects[pieceID]);
|
|
Graphics::ManagedSurface destSurf(_drawSurface, _destRects[pos]);
|
|
GraphicsManager::rotateBlit(srcSurf, destSurf, rotation);
|
|
}
|
|
|
|
bool RippedLetterPuzzle::checkOrder(bool useAlt) {
|
|
auto ¤t = _puzzleState->order;
|
|
auto &correct = useAlt ? _solveOrderAlt : _solveOrder;
|
|
|
|
for (uint i = 0; i < correct.size(); ++i) {
|
|
bool foundCorrect = false;
|
|
bool isDoubled = false;
|
|
for (auto &d : _doubles) {
|
|
for (byte e : d) {
|
|
if (current[i] == e) {
|
|
isDoubled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isDoubled) {
|
|
for (byte e : d) {
|
|
if (correct[i] == e) {
|
|
foundCorrect = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundCorrect) {
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isDoubled) {
|
|
if (current[i] != correct[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // End of namespace Action
|
|
} // End of namespace Nancy
|