NANCY: Implement rotating lock puzzle

Implemented the RotatingLockPuzzle action record.
This commit is contained in:
fracturehill 2021-02-23 01:26:10 +02:00 committed by Eugene Sandulenko
parent 725ff2f95d
commit c7e1c95803
8 changed files with 314 additions and 14 deletions

View File

@ -27,6 +27,7 @@
#include "engines/nancy/action/secondaryvideo.h"
#include "engines/nancy/action/staticbitmapanim.h"
#include "engines/nancy/action/orderingpuzzle.h"
#include "engines/nancy/action/rotatinglockpuzzle.h"
#include "engines/nancy/state/scene.h"
@ -114,7 +115,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
case 0x67:
return new DifficultyLevel();
case 0x68:
return new RotatingLockPuzzle();
return new RotatingLockPuzzle(_engine->scene->getViewport());
case 0x69:
return new LeverPuzzle();
case 0x6A:

View File

@ -233,4 +233,4 @@ void OrderingPuzzle::clearAllElements() {
}
} // End of namespace Action
} // End of namespace Nancy
} // End of namespace Nancy

View File

@ -41,7 +41,7 @@ class OrderingPuzzle : public ActionRecord, public RenderObject {
public:
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
OrderingPuzzle(RenderObject &redrawFrom) : RenderObject(redrawFrom) {}
~OrderingPuzzle() {}
virtual ~OrderingPuzzle() {}
virtual void init() override;
@ -60,7 +60,6 @@ public:
uint16 solveSoundDelay; // 0x22C
Nancy::SoundManager::SoundDescription solveSound; // 0x22E
SceneChangeDesc exitScene; // 0x250
//
FlagDesc flagOnExit; // 0x25A
Common::Rect exitHotspot; // 0x25D
@ -83,4 +82,4 @@ protected:
} // End of namespace Action
} // End of namespace Nancy
#endif // NANCY_ACTION_ORDERINGPUZZLE_H
#endif // NANCY_ACTION_ORDERINGPUZZLE_H

View File

@ -424,10 +424,6 @@ void DifficultyLevel::execute(NancyEngine *engine) {
isDone = true;
}
uint16 RotatingLockPuzzle::readData(Common::SeekableReadStream &stream) {
return readRaw(stream, 0x2A4); // TODO
}
void ShowInventoryItem::init() {
Graphics::Surface srcSurf;
_engine->_res->loadImage("ciftree", imageName, srcSurf);

View File

@ -312,11 +312,6 @@ public:
FlagDesc flag;
};
class RotatingLockPuzzle : public ActionRecord {
public:
virtual uint16 readData(Common::SeekableReadStream &stream) override;
};
class ShowInventoryItem : public ActionRecord, public RenderObject {
public:
virtual uint16 readData(Common::SeekableReadStream &stream) override;

View File

@ -0,0 +1,224 @@
/* 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/rotatinglockpuzzle.h"
#include "engines/nancy/util.h"
#include "engines/nancy/nancy.h"
#include "engines/nancy/resource.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/state/scene.h"
#include "common/random.h"
namespace Nancy {
namespace Action {
void RotatingLockPuzzle::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 RotatingLockPuzzle::readData(Common::SeekableReadStream &stream) {
char buf[10];
stream.read(buf, 10);
imageName = buf;
uint numDials = stream.readUint16LE();
for (uint i = 0; i < 10; ++i) {
srcRects.push_back(Common::Rect());
readRect(stream, srcRects.back());
}
for (uint i = 0; i < numDials; ++i) {
destRects.push_back(Common::Rect());
readRect(stream, destRects.back());
if (i == 0) {
_screenPosition = destRects.back();
} else {
_screenPosition.extend(destRects.back());
}
}
stream.skip((8 - numDials) * 16);
for (uint i = 0; i < numDials; ++i) {
upHotspots.push_back(Common::Rect());
readRect(stream, upHotspots.back());
}
stream.skip((8 - numDials) * 16);
for (uint i = 0; i < numDials; ++i) {
downHotspots.push_back(Common::Rect());
readRect(stream, downHotspots.back());
}
stream.skip((8 - numDials) * 16);
for (uint i = 0; i < numDials; ++i) {
correctSequence.push_back(stream.readByte());
}
stream.skip(8 - numDials);
clickSound.read(stream, SoundManager::SoundDescription::kNormal);
solveExitScene.readData(stream);
stream.skip(2); // shouldStopRendering, useless
flagOnSolve.label = stream.readUint16LE();
flagOnSolve.flag = (NancyFlag)stream.readByte();
solveSoundDelay = stream.readUint16LE();
solveSound.read(stream, SoundManager::SoundDescription::kNormal);
exitScene.readData(stream);
stream.skip(2); // shouldStopRendering, useless
flagOnExit.label = stream.readUint16LE();
flagOnExit.flag = (NancyFlag)stream.readByte();
readRect(stream, exitHotspot);
return 0x2A4;
}
void RotatingLockPuzzle::execute(Nancy::NancyEngine *engine) {
switch (state) {
case kBegin:
init();
registerGraphics();
for (uint i = 0; i < correctSequence.size(); ++i) {
currentSequence.push_back(_engine->_rnd->getRandomNumber(9));
drawDial(i);
}
_engine->sound->loadSound(clickSound);
_engine->sound->loadSound(solveSound);
state = kRun;
// fall through
case kRun:
switch (solveState) {
case kNotSolved:
for (uint i = 0; i < correctSequence.size(); ++i) {
if (currentSequence[i] != (int16)correctSequence[i]) {
return;
}
}
_engine->scene->setEventFlag(flagOnSolve.label, flagOnSolve.flag);
solveSoundPlayTime = _engine->getTotalPlayTime() + solveSoundDelay * 1000;
solveState = kPlaySound;
// fall through
case kPlaySound:
if (_engine->getTotalPlayTime() <= solveSoundPlayTime) {
break;
}
_engine->sound->playSound(solveSound.channelID);
solveState = kWaitForSound;
break;
case kWaitForSound:
if (!_engine->sound->isSoundPlaying(solveSound.channelID)) {
state = kActionTrigger;
}
break;
}
break;
case kActionTrigger:
_engine->sound->stopSound(clickSound.channelID);
_engine->sound->stopSound(solveSound.channelID);
if (solveState == kNotSolved) {
if (exitScene.sceneID != 9999) {
_engine->scene->changeScene(exitScene.sceneID, exitScene.frameID, exitScene.verticalOffset, exitScene.doNotStartSound);
_engine->scene->setEventFlag(flagOnExit.label, flagOnExit.flag);
}
} else {
if (solveExitScene.sceneID != 9999) {
_engine->scene->changeScene(solveExitScene.sceneID, solveExitScene.frameID, solveExitScene.verticalOffset, solveExitScene.doNotStartSound);
}
}
isDone = true;
}
}
void RotatingLockPuzzle::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;
}
for (uint i = 0; i < upHotspots.size(); ++i) {
if (_engine->scene->getViewport().convertViewportToScreen(upHotspots[i]).contains(input.mousePos)) {
_engine->cursorManager->setCursorType(CursorManager::kHotspot);
if (input.input & NancyInput::kLeftMouseButtonUp) {
_engine->sound->playSound(clickSound.channelID);
currentSequence[i] = ++currentSequence[i] > 9 ? 0 : currentSequence[i];
drawDial(i);
}
return;
}
}
for (uint i = 0; i < downHotspots.size(); ++i) {
if (_engine->scene->getViewport().convertViewportToScreen(downHotspots[i]).contains(input.mousePos)) {
_engine->cursorManager->setCursorType(CursorManager::kHotspot);
if (input.input & NancyInput::kLeftMouseButtonUp) {
_engine->sound->playSound(clickSound.channelID);
int8 n = currentSequence[i];
n = --n < 0 ? 9 : n;
currentSequence[i] = n;
drawDial(i);
}
return;
}
}
}
void RotatingLockPuzzle::drawDial(uint id) {
Common::Point destPoint(destRects[id].left - _screenPosition.left, destRects[id].top - _screenPosition.top);
_drawSurface.blitFrom(image, srcRects[currentSequence[id]], destPoint);
_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_ROTATINGLOCKPUZZLE_H
#define NANCY_ACTION_ROTATINGLOCKPUZZLE_H
#include "engines/nancy/action/recordtypes.h"
#include "engines/nancy/renderobject.h"
#include "engines/nancy/sound.h"
#include "engines/nancy/time.h"
#include "common/str.h"
#include "common/array.h"
#include "common/rect.h"
namespace Nancy {
namespace Action {
class RotatingLockPuzzle : public ActionRecord, public RenderObject {
public:
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
RotatingLockPuzzle(RenderObject &redrawFrom) : RenderObject(redrawFrom) {}
virtual ~RotatingLockPuzzle() {}
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
// 0xA numDials
Common::Array<Common::Rect> srcRects; // 0xC, 10
Common::Array<Common::Rect> destRects; // 0xAC, 8
Common::Array<Common::Rect> upHotspots; // 0x12C, 8
Common::Array<Common::Rect> downHotspots; // 0x1AC, 8
Common::Array<byte> correctSequence; // 0x22C
Nancy::SoundManager::SoundDescription clickSound; // 0x234, kNormal
SceneChangeDesc solveExitScene; // 0x256
FlagDesc flagOnSolve; // 0x260
uint16 solveSoundDelay; // 0x263
Nancy::SoundManager::SoundDescription solveSound; // 0x265
SceneChangeDesc exitScene; // 0x287
FlagDesc flagOnExit; // 0x291
Common::Rect exitHotspot; // 0x294
SolveState solveState = kNotSolved;
Graphics::ManagedSurface image;
Common::Array<byte> currentSequence;
Time solveSoundPlayTime;
protected:
virtual uint16 getZOrder() const override { return 7; }
virtual BlitType getBlitType() const override { return kTrans; }
virtual bool isViewportRelative() const override { return true; }
void drawDial(uint id);
};
} // End of namespace Action
} // End of namespace Nancy
#endif // NANCY_ACTION_ROTATINGLOCKPUZZLE_H

View File

@ -6,6 +6,7 @@ MODULE_OBJS = \
action/orderingpuzzle.o \
action/primaryvideo.o \
action/recordtypes.o \
action/rotatinglockpuzzle.o \
action/secondaryvideo.o \
action/staticbitmapanim.o \
ui/frame.o \