NANCY: Implement slider puzzle

Implemented the SliderPuzzle action record, which is used for the jewel box in the safe in nancy1.
This commit is contained in:
fracturehill 2021-02-27 00:05:42 +02:00 committed by Eugene Sandulenko
parent d83edfdca3
commit 1cd406b5af
7 changed files with 399 additions and 14 deletions

View File

@ -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:

View File

@ -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
}

View File

@ -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;

View File

@ -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<Common::Array<int16>> SliderPuzzle::playerTileOrder = Common::Array<Common::Array<int16>>();
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<Common::Rect>());
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<Common::Rect>());
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<int16>());
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<int16>());
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

View File

@ -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<Common::Array<Common::Rect>> srcRects; // 0x0E, size 0x240
Common::Array<Common::Array<Common::Rect>> destRects; // 0x24E, size 0x240
Common::Array<Common::Array<int16>> 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<Common::Array<int16>> 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

View File

@ -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 \

View File

@ -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