From 1cd406b5af9cb2e020a18880ba3ac527c83da956 Mon Sep 17 00:00:00 2001 From: fracturehill Date: Sat, 27 Feb 2021 00:05:42 +0200 Subject: [PATCH] NANCY: Implement slider puzzle Implemented the SliderPuzzle action record, which is used for the jewel box in the safe in nancy1. --- engines/nancy/action/arfactory_v1.cpp | 3 +- engines/nancy/action/recordtypes.cpp | 4 - engines/nancy/action/recordtypes.h | 5 - engines/nancy/action/sliderpuzzle.cpp | 308 ++++++++++++++++++++++++++ engines/nancy/action/sliderpuzzle.h | 84 +++++++ engines/nancy/module.mk | 1 + engines/nancy/util.h | 8 +- 7 files changed, 399 insertions(+), 14 deletions(-) create mode 100644 engines/nancy/action/sliderpuzzle.cpp create mode 100644 engines/nancy/action/sliderpuzzle.h diff --git a/engines/nancy/action/arfactory_v1.cpp b/engines/nancy/action/arfactory_v1.cpp index f925890c34a..63442e265a0 100644 --- a/engines/nancy/action/arfactory_v1.cpp +++ b/engines/nancy/action/arfactory_v1.cpp @@ -29,6 +29,7 @@ #include "engines/nancy/action/orderingpuzzle.h" #include "engines/nancy/action/rotatinglockpuzzle.h" #include "engines/nancy/action/telephone.h" +#include "engines/nancy/action/sliderpuzzle.h" #include "engines/nancy/state/scene.h" @@ -122,7 +123,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) { case 0x6A: return new Telephone(_engine->scene->getViewport()); case 0x6B: - return new SliderPuzzle(); + return new SliderPuzzle(_engine->scene->getViewport()); case 0x6C: return new PasswordPuzzle(); case 0x6E: diff --git a/engines/nancy/action/recordtypes.cpp b/engines/nancy/action/recordtypes.cpp index 58213325ae0..3eb85bf3216 100644 --- a/engines/nancy/action/recordtypes.cpp +++ b/engines/nancy/action/recordtypes.cpp @@ -387,10 +387,6 @@ uint16 LeverPuzzle::readData(Common::SeekableReadStream &stream) { return readRaw(stream, 0x192); // TODO } -uint16 SliderPuzzle::readData(Common::SeekableReadStream &stream) { - return readRaw(stream, 0x544); // TODO -} - uint16 PasswordPuzzle::readData(Common::SeekableReadStream &stream) { return readRaw(stream, 0xD7); // TODO } diff --git a/engines/nancy/action/recordtypes.h b/engines/nancy/action/recordtypes.h index e3517485aef..ac4d821a8cf 100644 --- a/engines/nancy/action/recordtypes.h +++ b/engines/nancy/action/recordtypes.h @@ -278,11 +278,6 @@ public: virtual uint16 readData(Common::SeekableReadStream &stream) override; }; -class SliderPuzzle : public ActionRecord { -public: - virtual uint16 readData(Common::SeekableReadStream &stream) override; -}; - class PasswordPuzzle : public ActionRecord { public: virtual uint16 readData(Common::SeekableReadStream &stream) override; diff --git a/engines/nancy/action/sliderpuzzle.cpp b/engines/nancy/action/sliderpuzzle.cpp new file mode 100644 index 00000000000..e054df242a2 --- /dev/null +++ b/engines/nancy/action/sliderpuzzle.cpp @@ -0,0 +1,308 @@ +/* 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/nancy/action/sliderpuzzle.h" + +#include "engines/nancy/util.h" +#include "engines/nancy/nancy.h" +#include "engines/nancy/graphics.h" +#include "engines/nancy/resource.h" +#include "engines/nancy/sound.h" +#include "engines/nancy/state/scene.h" + +namespace Nancy { +namespace Action { + +Common::Array> SliderPuzzle::playerTileOrder = Common::Array>(); +bool SliderPuzzle::playerHasTriedPuzzle = false; + +void SliderPuzzle::init() { + _drawSurface.create(_screenPosition.width(), _screenPosition.height(), GraphicsManager::pixelFormat); + _drawSurface.clear(GraphicsManager::transColor); + + Graphics::Surface surf; + _engine->_res->loadImage("ciftree", imageName, surf); + image.create(surf.w, surf.h, surf.format); + image.blitFrom(surf); + surf.free(); +} + +uint16 SliderPuzzle::readData(Common::SeekableReadStream &stream) { + char buf[10]; + stream.read(buf, 10); + imageName = buf; + + width = stream.readUint16LE(); + height = stream.readUint16LE(); + + for (uint y = 0; y < height; ++y) { + srcRects.push_back(Common::Array()); + for (uint x = 0; x < width; ++x) { + srcRects.back().push_back(Common::Rect()); + readRect(stream, srcRects.back().back()); + } + stream.skip((6 - width) * 16); + } + + stream.skip((6 - height) * 6 * 16); + + for (uint y = 0; y < height; ++y) { + destRects.push_back(Common::Array()); + for (uint x = 0; x < width; ++x) { + destRects.back().push_back(Common::Rect()); + readRect(stream, destRects.back().back()); + + if (x == 0 && y == 0) { + _screenPosition = destRects.back().back(); + } else { + _screenPosition.extend(destRects.back().back()); + } + } + stream.skip((6 - width) * 16); + } + + stream.skip((6 - height) * 6 * 16); + + for (uint y = 0; y < height; ++y) { + correctTileOrder.push_back(Common::Array()); + for (uint x = 0; x < width; ++x) { + correctTileOrder.back().push_back(stream.readSint16LE()); + } + stream.skip((6 - width) * 2); + } + + stream.skip((6 - height) * 6 * 2); + + clickSound.read(stream, SoundManager::SoundDescription::kNormal); + solveExitScene.readData(stream); + stream.skip(2); + flagOnSolve.label = stream.readUint16LE(); + flagOnSolve.flag = (NancyFlag)stream.readByte(); + solveSound.read(stream, SoundManager::SoundDescription::kNormal); + exitScene.readData(stream); + stream.skip(2); + flagOnExit.label = stream.readUint16LE(); + flagOnExit.flag = (NancyFlag)stream.readByte(); + readRect(stream, exitHotspot); + + return 0x544; +} + +void SliderPuzzle::execute(Nancy::NancyEngine *engine) { + switch (state) { + case kBegin: + init(); + registerGraphics(); + if (!playerHasTriedPuzzle) { + Common::SeekableReadStream *spuz = engine->getBootChunkStream("SPUZ"); + playerTileOrder.clear(); + spuz->seek(engine->scene->getDifficulty() * 0x48); + for (uint y = 0; y < height; ++y) { + playerTileOrder.push_back(Common::Array()); + for (uint x = 0; x < width; ++x) { + playerTileOrder.back().push_back(spuz->readSint16LE()); + } + spuz->skip((6 - width) * 2); + } + playerHasTriedPuzzle = true; + } + + for (uint y = 0; y < height; ++y) { + for (uint x = 0; x < width; ++x) { + if (!srcRects[y][x].isEmpty()) { + drawTile(playerTileOrder[y][x], x, y); + } + } + } + + engine->sound->loadSound(clickSound); + state = kRun; + // fall through + case kRun: + switch (solveState) { + case kNotSolved: + for (uint y = 0; y < height; ++y) { + for (uint x = 0; x < width; ++x) { + if (playerTileOrder[y][x] != correctTileOrder[y][x]) { + return; + } + } + } + + engine->sound->loadSound(solveSound); + engine->sound->playSound(solveSound.channelID); + solveState = kWaitForSound; + break; + case kWaitForSound: + if (!engine->sound->isSoundPlaying(solveSound.channelID)) { + engine->sound->stopSound(solveSound.channelID); + state = kActionTrigger; + } + + break; + } + + break; + case kActionTrigger: + switch (solveState) { + case kNotSolved: + if (exitScene.sceneID != 9999) { + engine->scene->changeScene(exitScene.sceneID, exitScene.frameID, exitScene.verticalOffset, exitScene.doNotStartSound); + } + + engine->scene->setEventFlag(flagOnExit.label, flagOnExit.flag); + break; + case kWaitForSound: + if (exitScene.sceneID != 9999) { + engine->scene->changeScene(solveExitScene.sceneID, solveExitScene.frameID, solveExitScene.verticalOffset, solveExitScene.doNotStartSound); + } + + engine->scene->setEventFlag(flagOnSolve.label, flagOnSolve.flag); + playerHasTriedPuzzle = false; + break; + } + + engine->sound->stopSound(clickSound.channelID); + isDone = true; + } +} + +void SliderPuzzle::handleInput(NancyInput &input) { + if (solveState != kNotSolved) { + return; + } + + if (_engine->scene->getViewport().convertViewportToScreen(exitHotspot).contains(input.mousePos)) { + _engine->cursorManager->setCursorType(CursorManager::kExitArrow); + + if (input.input & NancyInput::kLeftMouseButtonUp) { + state = kActionTrigger; + } + return; + } + + int currentTileX = -1; + int currentTileY = -1; + uint direction = 0; + for (uint y = 0; y < height; ++y) { + bool shouldBreak = false; + for (uint x = 0; x < width; ++x) { + if (x > 0 && playerTileOrder[y][x - 1] < 0) { + if (_engine->scene->getViewport().convertViewportToScreen(destRects[y][x]).contains(input.mousePos)) { + currentTileX = x; + currentTileY = y; + direction = kLeft; + shouldBreak = true; + break; + } + } else if ((int)x < width - 1 && playerTileOrder[y][x + 1] < 0) { + if (_engine->scene->getViewport().convertViewportToScreen(destRects[y][x]).contains(input.mousePos)) { + currentTileX = x; + currentTileY = y; + direction = kRight; + shouldBreak = true; + break; + } + } else if (y > 0 && playerTileOrder[y - 1][x] < 0) { + if (_engine->scene->getViewport().convertViewportToScreen(destRects[y][x]).contains(input.mousePos)) { + currentTileX = x; + currentTileY = y; + direction = kUp; + shouldBreak = true; + break; + } + } else if ((int)y < height - 1 && playerTileOrder[y + 1][x] < 0) { + if (_engine->scene->getViewport().convertViewportToScreen(destRects[y][x]).contains(input.mousePos)) { + currentTileX = x; + currentTileY = y; + direction = kDown; + shouldBreak = true; + break; + } + } + } + + if (shouldBreak) { + break; + } + } + + if (currentTileX != -1) { + _engine->cursorManager->setCursorType(CursorManager::kHotspot); + + if (input.input & NancyInput::kLeftMouseButtonUp) { + _engine->sound->playSound(clickSound.channelID); + switch (direction) { + case kUp: { + uint curTileID = playerTileOrder[currentTileY][currentTileX]; + drawTile(curTileID, currentTileX, currentTileY - 1); + undrawTile(currentTileX, currentTileY); + playerTileOrder[currentTileY - 1][currentTileX] = curTileID; + playerTileOrder[currentTileY][currentTileX] = -10; + break; + } + case kDown: { + uint curTileID = playerTileOrder[currentTileY][currentTileX]; + drawTile(curTileID, currentTileX, currentTileY + 1); + undrawTile(currentTileX, currentTileY); + playerTileOrder[currentTileY + 1][currentTileX] = curTileID; + playerTileOrder[currentTileY][currentTileX] = -10; + break; + } + case kLeft: { + uint curTileID = playerTileOrder[currentTileY][currentTileX]; + drawTile(curTileID, currentTileX - 1, currentTileY); + undrawTile(currentTileX, currentTileY); + playerTileOrder[currentTileY][currentTileX - 1] = curTileID; + playerTileOrder[currentTileY][currentTileX] = -10; + break; + } + case kRight: { + uint curTileID = playerTileOrder[currentTileY][currentTileX]; + drawTile(curTileID, currentTileX + 1, currentTileY); + undrawTile(currentTileX, currentTileY); + playerTileOrder[currentTileY][currentTileX + 1] = curTileID; + playerTileOrder[currentTileY][currentTileX] = -10; + break; + } + } + } + } +} + +void SliderPuzzle::drawTile(uint tileID, uint posX, uint posY) { + Common::Point destPoint(destRects[posY][posX].left - _screenPosition.left, destRects[posY][posX].top - _screenPosition.top); + _drawSurface.blitFrom(image, srcRects[tileID / height][tileID % width], destPoint); + + _needsRedraw = true; +} + +void SliderPuzzle::undrawTile(uint posX, uint posY) { + Common::Rect bounds = destRects[posY][posX]; + bounds.translate(-_screenPosition.left, -_screenPosition.top); + _drawSurface.fillRect(bounds, GraphicsManager::transColor); + + _needsRedraw = true; +} + +} // End of namespace Action +} // End of namespace Nancy diff --git a/engines/nancy/action/sliderpuzzle.h b/engines/nancy/action/sliderpuzzle.h new file mode 100644 index 00000000000..2c36a0494da --- /dev/null +++ b/engines/nancy/action/sliderpuzzle.h @@ -0,0 +1,84 @@ +/* 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 NANCY_ACTION_SLIDERPUZZLE_H +#define NANCY_ACTION_SLIDERPUZZLE_H + +#include "engines/nancy/action/recordtypes.h" +#include "engines/nancy/renderobject.h" + +#include "engines/nancy/sound.h" + +#include "common/str.h" +#include "common/array.h" +#include "common/rect.h" + +#include "graphics/managed_surface.h" + +namespace Nancy { +namespace Action { + +class SliderPuzzle: public ActionRecord, public RenderObject { +public: + enum SolveState { kNotSolved, kWaitForSound }; + SliderPuzzle(RenderObject &redrawFrom) : RenderObject(redrawFrom) {} + virtual ~SliderPuzzle() {} + + virtual void init() override; + + virtual uint16 readData(Common::SeekableReadStream &stream) override; + virtual void execute(Nancy::NancyEngine *engine) override; + virtual void handleInput(NancyInput &input) override; + + Common::String imageName; // 0x00 + uint16 width; // 0xA + uint16 height; // 0xC + Common::Array> srcRects; // 0x0E, size 0x240 + Common::Array> destRects; // 0x24E, size 0x240 + Common::Array> correctTileOrder; // 0x48E, size 0x48 + SoundManager::SoundDescription clickSound; // 0x4D6 + SceneChangeDesc solveExitScene; // 0x4F8 + FlagDesc flagOnSolve; // 0x502 + SoundManager::SoundDescription solveSound; // 0x505 + SceneChangeDesc exitScene; // 0x527 + FlagDesc flagOnExit; // 0x531 + Common::Rect exitHotspot; // 0x534 + + SolveState solveState = kNotSolved; + Graphics::ManagedSurface image; + + static Common::Array> playerTileOrder; + static bool playerHasTriedPuzzle; + +protected: + virtual uint16 getZOrder() const override { return 7; } + virtual BlitType getBlitType() const override { return kTrans; } + virtual bool isViewportRelative() const override { return true; } + + void drawTile(uint tileID, uint posX, uint posY); + void undrawTile(uint posX, uint posY); +}; + +} // End of namespace Action +} // End of namespace Nancy + +#endif // NANCY_ACTION_SLIDERPUZZLE_H diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk index 8d3eeca3453..205d0561ba0 100644 --- a/engines/nancy/module.mk +++ b/engines/nancy/module.mk @@ -8,6 +8,7 @@ MODULE_OBJS = \ action/recordtypes.o \ action/rotatinglockpuzzle.o \ action/secondaryvideo.o \ + action/sliderpuzzle.o \ action/staticbitmapanim.o \ action/telephone.o \ ui/frame.o \ diff --git a/engines/nancy/util.h b/engines/nancy/util.h index 7f561718a56..871130ff97b 100644 --- a/engines/nancy/util.h +++ b/engines/nancy/util.h @@ -28,10 +28,10 @@ namespace Nancy { static void readRect(Common::SeekableReadStream &stream, Common::Rect &inRect) { - inRect.left = stream.readUint32LE(); - inRect.top = stream.readUint32LE(); - inRect.right = stream.readUint32LE(); - inRect.bottom = stream.readUint32LE(); + inRect.left = stream.readSint32LE(); + inRect.top = stream.readSint32LE(); + inRect.right = stream.readSint32LE(); + inRect.bottom = stream.readSint32LE(); } } // End of namespace Nancy