Initial commit
This commit is contained in:
99
engines/nancy/action/puzzle/angletosspuzzle.cpp
Normal file
99
engines/nancy/action/puzzle/angletosspuzzle.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/angletosspuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void AngleTossPuzzle::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);
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the winning screen
|
||||
warning("STUB - Nancy 8 Squid Toss game");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4465;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Path tmp;
|
||||
readFilename(stream, tmp);
|
||||
stream.skip(12); // TODO
|
||||
|
||||
for (uint i = 0; i < 22; ++i) {
|
||||
Common::Rect r;
|
||||
readRect(stream, r);
|
||||
|
||||
/*
|
||||
Common::String desc = Common::String::format("AngleTossPuzzle rect %d", i);
|
||||
debug("%s %d, %d, %d, %d", desc.c_str(), r.left, r.top, r.right, r.bottom);
|
||||
|
||||
Graphics::Surface *s = g_system->lockScreen();
|
||||
s->fillRect(r, 255);
|
||||
g_system->unlockScreen();
|
||||
g_system->updateScreen();
|
||||
g_system->delayMillis(1000);
|
||||
*/
|
||||
}
|
||||
|
||||
_powerSound.readNormal(stream);
|
||||
_squeakSound.readNormal(stream);
|
||||
_chainSound.readNormal(stream);
|
||||
|
||||
_throwSquidScene.readData(stream);
|
||||
stream.skip(7); // TODO
|
||||
_exitScene.readData(stream);
|
||||
|
||||
stream.skip(16); // TODO
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
58
engines/nancy/action/puzzle/angletosspuzzle.h
Normal file
58
engines/nancy/action/puzzle/angletosspuzzle.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
#define NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Squid Toss mini-game in Nancy 8
|
||||
|
||||
class AngleTossPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
AngleTossPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~AngleTossPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "AngleTossPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
SoundDescription _powerSound;
|
||||
SoundDescription _squeakSound;
|
||||
SoundDescription _chainSound;
|
||||
|
||||
SceneChangeWithFlag _throwSquidScene;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
65
engines/nancy/action/puzzle/arcadepuzzle.cpp
Normal file
65
engines/nancy/action/puzzle/arcadepuzzle.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/arcadepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void ArcadePuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void ArcadePuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the winning screen
|
||||
warning("STUB - Nancy 8 Barnacle Blast game");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4445;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void ArcadePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void ArcadePuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/arcadepuzzle.h
Normal file
51
engines/nancy/action/puzzle/arcadepuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ARCADEPUZZLE_H
|
||||
#define NANCY_ACTION_ARCADEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Barnacle Blast (Arcanoid) mini-game in Nancy 8
|
||||
|
||||
class ArcadePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
ArcadePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~ArcadePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ArcadePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ARCADEPUZZLE_H
|
||||
334
engines/nancy/action/puzzle/assemblypuzzle.cpp
Normal file
334
engines/nancy/action/puzzle/assemblypuzzle.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/assemblypuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void AssemblyPuzzle::init() {
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
piece.curRotation = piece.placed ? piece.correctRotation : 0;
|
||||
piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
|
||||
piece.setVisible(true);
|
||||
piece.setTransparent(true);
|
||||
piece.moveTo(piece.placed ? piece.destRects[piece.curRotation] : piece.startRect);
|
||||
piece.setZ(_z + i + _pieces.size());
|
||||
}
|
||||
|
||||
rotateBase(true);
|
||||
rotateBase(false);
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (AssemblyPuzzleData *)NancySceneState.getPuzzleData(AssemblyPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 numPieces = stream.readUint16LE();
|
||||
_height = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _cwCursorDest);
|
||||
readRect(stream, _ccwCursorDest);
|
||||
|
||||
_pieces.resize(numPieces);
|
||||
for (uint i = 0; i < numPieces; ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
readRectArray(stream, piece.srcRects, 4);
|
||||
readRectArray(stream, piece.destRects, 4);
|
||||
|
||||
readRect(stream, piece.startRect);
|
||||
|
||||
piece.correctRotation = stream.readUint16LE();
|
||||
piece.layer = stream.readUint16LE();
|
||||
piece.placed = stream.readUint16LE();
|
||||
|
||||
if (_puzzleState->solvedPuzzle) {
|
||||
piece.placed = true;
|
||||
}
|
||||
}
|
||||
stream.skip((12 - numPieces) * 150);
|
||||
|
||||
_rotateSound.readNormal(stream);
|
||||
_pickUpSound.readNormal(stream);
|
||||
_placeDownSound.readNormal(stream);
|
||||
|
||||
_allowWrongPieceHotspot = stream.readUint16LE();
|
||||
|
||||
_wrongPieceSounds.resize(4);
|
||||
_wrongPieceTexts.resize(4);
|
||||
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_wrongPieceSounds[i].readNormal(stream);
|
||||
}
|
||||
|
||||
char buf[200];
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
stream.read(buf, 200);
|
||||
assembleTextLine(buf, _wrongPieceTexts[i], 200);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
stream.read(buf, 200);
|
||||
assembleTextLine(buf, _solveText, 200);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (AssemblyPuzzleData *)NancySceneState.getPuzzleData(AssemblyPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_placeDownSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_layersAssembled != _height) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_solveText);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_completed = true;
|
||||
|
||||
_state = kActionTrigger;
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
_puzzleState->solvedPuzzle = true;
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state == kActionTrigger && _completed && g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_completed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_pieces[i].startRect).contains(input.mousePos)) {
|
||||
if (_pickedUpPiece == -1 && _pieces[i].placed) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_pickedUpPiece != -1) {
|
||||
_pieces[_pickedUpPiece].putDown();
|
||||
_pieces[_pickedUpPiece].moveTo(_pieces[_pickedUpPiece].startRect);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != (int)i && !_pieces[i].placed) {
|
||||
// Clicked on another piece while holding, swap them
|
||||
_pickedUpPiece = i;
|
||||
_pieces[i].pickUp();
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
|
||||
for (uint j = 1; j < _pieces.size(); ++j) {
|
||||
Piece &piece = _pieces[j];
|
||||
if (!piece.placed && piece.getZOrder() > _pieces[i].getZOrder()) {
|
||||
piece.setZ(piece.getZOrder() - 1);
|
||||
piece.registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
_pieces[i].setZ(_z + _pieces.size() * 2);
|
||||
_pieces[i].registerGraphics();
|
||||
} else {
|
||||
// Clicked the dest of the picked up piece, or an already placed one; simply put it down
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
// Piece picked up
|
||||
Piece &pickedUpPiece = _pieces[_pickedUpPiece];
|
||||
pickedUpPiece.handleInput(input);
|
||||
|
||||
bool isWrong = _curRotation != pickedUpPiece.correctRotation;
|
||||
bool otherIsPlaced = false;
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i].placed && _pieces[i].curRotation == 0 && _pieces[i].layer - 1 == (int)_layersAssembled) {
|
||||
otherIsPlaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!otherIsPlaced && (!isWrong || (_allowWrongPieceHotspot && _layersAssembled + 1 == pickedUpPiece.layer))) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(pickedUpPiece.destRects[0]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (!isWrong) {
|
||||
pickedUpPiece.putDown();
|
||||
pickedUpPiece.moveTo(pickedUpPiece.destRects[0]);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
pickedUpPiece.placed = true;
|
||||
_pickedUpPiece = -1;
|
||||
|
||||
// Check for finished layer
|
||||
uint placedOnLayer = 0;
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i].layer == _layersAssembled + 1 && _pieces[i].placed) {
|
||||
++placedOnLayer;
|
||||
}
|
||||
}
|
||||
|
||||
if (placedOnLayer == 4) {
|
||||
++_layersAssembled;
|
||||
}
|
||||
} else if (_allowWrongPieceHotspot) {
|
||||
// Wrong place, play a sound
|
||||
g_nancy->_sound->loadSound(_wrongPieceSounds[_curRotation]);
|
||||
g_nancy->_sound->playSound(_wrongPieceSounds[_curRotation]);
|
||||
if (!_wrongPieceTexts[_curRotation].empty()) {
|
||||
NancySceneState.getTextbox().addTextLine(_wrongPieceTexts[_curRotation], 4000); // check
|
||||
}
|
||||
|
||||
// and put down the piece
|
||||
pickedUpPiece.putDown();
|
||||
pickedUpPiece.moveTo(pickedUpPiece.startRect);
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::rotateBase(bool ccw) {
|
||||
// _curRotation moves in the opposite direction to pieces' rotations
|
||||
_curRotation += ccw ? 1 : -1;
|
||||
if (_curRotation < 0) {
|
||||
_curRotation = 3;
|
||||
} else if (_curRotation > 3) {
|
||||
_curRotation = 0;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
if (piece.placed) {
|
||||
piece.curRotation += ccw ? -1 : 1;
|
||||
if (piece.curRotation < 0) {
|
||||
piece.curRotation = 3;
|
||||
} else if (piece.curRotation > 3) {
|
||||
piece.curRotation = 0;
|
||||
}
|
||||
|
||||
uint base = 0;
|
||||
if (piece.curRotation == 0) {
|
||||
base = 2;
|
||||
} else if (piece.curRotation == 1 || piece.curRotation == 3) {
|
||||
base = 1;
|
||||
}
|
||||
|
||||
piece.setZ(_z + base + 4 * (piece.layer - 1));
|
||||
piece.registerGraphics();
|
||||
|
||||
piece.moveTo(piece.destRects[piece.curRotation]);
|
||||
piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
|
||||
piece.setTransparent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
104
engines/nancy/action/puzzle/assemblypuzzle.h
Normal file
104
engines/nancy/action/puzzle/assemblypuzzle.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
#define NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct AssemblyPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
// Minigame where the player is provided with the broken pieces of something
|
||||
// (a piece of pottery in nancy6), and has to assemble it.
|
||||
class AssemblyPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
AssemblyPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~AssemblyPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "AssemblyPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void rotateBase(bool ccw);
|
||||
|
||||
struct Piece : Misc::MouseFollowObject {
|
||||
Common::Array<Common::Rect> srcRects;
|
||||
Common::Array<Common::Rect> destRects;
|
||||
|
||||
Common::Rect startRect;
|
||||
uint16 correctRotation = 0;
|
||||
uint16 layer = 0;
|
||||
bool placed = false;
|
||||
|
||||
int curRotation = 0;
|
||||
};
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _height = 0;
|
||||
|
||||
Common::Rect _cwCursorDest;
|
||||
Common::Rect _ccwCursorDest;
|
||||
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
SoundDescription _rotateSound;
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _placeDownSound;
|
||||
|
||||
bool _allowWrongPieceHotspot = false;
|
||||
|
||||
Common::Array<SoundDescription> _wrongPieceSounds;
|
||||
Common::Array<Common::String> _wrongPieceTexts;
|
||||
|
||||
SceneChangeWithFlag _solveScene; // has 9999 in nancy6, so the puzzle doesn't auto-exit
|
||||
SoundDescription _solveSound;
|
||||
Common::String _solveText;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int _pickedUpPiece = -1;
|
||||
int _curRotation = 0;
|
||||
uint _layersAssembled = 0;
|
||||
bool _completed = false;
|
||||
|
||||
AssemblyPuzzleData *_puzzleState = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
299
engines/nancy/action/puzzle/bballpuzzle.cpp
Normal file
299
engines/nancy/action/puzzle/bballpuzzle.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bballpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BBallPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
// Set up flags
|
||||
if (NancySceneState.getEventFlag(_goodShootFlag, g_nancy->_true)) {
|
||||
// Last shot entered the hoop
|
||||
for (uint i = 0; i < _playerPositionFlags.size(); ++i) {
|
||||
if (NancySceneState.getEventFlag(_playerPositionFlags[i], g_nancy->_true)) {
|
||||
_curPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unset last position flag
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[_curPosition], g_nancy->_false);
|
||||
|
||||
if ((int)_curPosition == _positions - 1) {
|
||||
// Beat the game once, reset to initial
|
||||
_curPosition = 0;
|
||||
} else {
|
||||
// In the middle of the game, move to next position
|
||||
++_curPosition;
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[_curPosition], g_nancy->_true);
|
||||
} else {
|
||||
// Last shot did not enter the hoop, reset to initial position
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[0], g_nancy->_true);
|
||||
|
||||
for (uint i = 1; i < _playerPositionFlags.size(); ++i) {
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[i], g_nancy->_false);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset shot type flags
|
||||
for (uint i = 0; i < _badShootFlags.size(); ++i) {
|
||||
NancySceneState.setEventFlag(_badShootFlags[i], g_nancy->_false);
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_goodShootFlag, g_nancy->_false);
|
||||
|
||||
// Draw the current player position
|
||||
if (_curPosition > 0) {
|
||||
_drawSurface.blitFrom(_image, _playerSrcs[_curPosition - 1], _playerDest);
|
||||
}
|
||||
}
|
||||
|
||||
void BBallPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_positions = stream.readUint16LE();
|
||||
_powers = stream.readUint16LE();
|
||||
_angles = stream.readUint16LE();
|
||||
|
||||
_correctVals.resize(_positions);
|
||||
for (uint i = 0; i < _positions; ++i) {
|
||||
_correctVals[i].x = stream.readUint16LE();
|
||||
_correctVals[i].y = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _shootButtonDest);
|
||||
readRect(stream, _minusButtonDest);
|
||||
readRect(stream, _plusButtonDest);
|
||||
|
||||
readRect(stream, _playerDest);
|
||||
readRect(stream, _powerDest);
|
||||
readRect(stream, _angleDest);
|
||||
|
||||
readRectArray(stream, _angleSliderHotspots, 3);
|
||||
|
||||
readRect(stream, _shootButtonSrc);
|
||||
readRect(stream, _minusButtonSrc);
|
||||
readRect(stream, _plusButtonSrc);
|
||||
|
||||
readRectArray(stream, _playerSrcs, 3);
|
||||
readRectArray(stream, _powerSrcs, 5);
|
||||
readRectArray(stream, _anglesSrcs, 2);
|
||||
|
||||
_shootSound.readNormal(stream);
|
||||
_minusSound.readNormal(stream);
|
||||
_plusSound.readNormal(stream);
|
||||
|
||||
_shootSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
|
||||
_badShootFlags.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_badShootFlags[i] = stream.readSint16LE();
|
||||
}
|
||||
|
||||
_goodShootFlag = stream.readSint16LE();
|
||||
|
||||
_playerPositionFlags.resize(_positions);
|
||||
for (uint i = 0; i < _positions; ++i) {
|
||||
_playerPositionFlags[i] = stream.readSint16LE();
|
||||
}
|
||||
|
||||
_winFlag = stream.readUint16LE();
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void BBallPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_plusSound);
|
||||
g_nancy->_sound->loadSound(_minusSound);
|
||||
g_nancy->_sound->loadSound(_shootSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_pressedButton) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_plusSound) || g_nancy->_sound->isSoundPlaying(_minusSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pressedButton = false;
|
||||
_drawSurface.fillRect(_powerDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_plusButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_minusButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (_curPower > 0) {
|
||||
_drawSurface.blitFrom(_image, _powerSrcs[_curPower - 1], _powerDest);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_pressedButton) {
|
||||
// Pressed the shoot button
|
||||
if (g_nancy->_sound->isSoundPlaying(_shootSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16 flagToSet = -1;
|
||||
|
||||
if ((int)_curPower == _correctVals[_curPosition].x && (int)_curAngle == _correctVals[_curPosition].y) {
|
||||
// Selected correct values
|
||||
flagToSet = _goodShootFlag;
|
||||
|
||||
if ((int)_curPosition == _positions - 1) {
|
||||
// Last throw, mark puzzle as solved
|
||||
NancySceneState.setEventFlag(_winFlag, g_nancy->_true);
|
||||
}
|
||||
} else if (_curPower == 0) {
|
||||
// Low throw
|
||||
flagToSet = _badShootFlags[2];
|
||||
} else if ((int)_curPower >= _correctVals[_curPosition].x && (int)_curAngle <= _correctVals[_curPosition].y) {
|
||||
// High throw
|
||||
flagToSet = _badShootFlags[0];
|
||||
} else {
|
||||
// Mid throw
|
||||
flagToSet = _badShootFlags[1];
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(flagToSet, g_nancy->_true);
|
||||
NancySceneState.changeScene(_shootSceneChange);
|
||||
} else {
|
||||
// Exited the puzzle
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_plusSound);
|
||||
g_nancy->_sound->stopSound(_minusSound);
|
||||
g_nancy->_sound->stopSound(_shootSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void BBallPuzzle::handleInput(NancyInput &input) {
|
||||
Common::Point localMousePos = input.mousePos;
|
||||
Common::Rect vpPos = NancySceneState.getViewport().getScreenPosition();
|
||||
localMousePos -= { vpPos.left, vpPos.top };
|
||||
|
||||
if (_exitHotspot.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (!_pressedButton &&input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _angleSliderHotspots.size(); ++i) {
|
||||
if (_curAngle != i && _angleSliderHotspots[i].contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.fillRect(_angleDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (i > 0) {
|
||||
_drawSurface.blitFrom(_image, _anglesSrcs[i - 1], _angleDest);
|
||||
}
|
||||
|
||||
_curAngle = i;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_curPower > 0 && _minusButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_curPower;
|
||||
_drawSurface.blitFrom(_image, _minusButtonSrc, _minusButtonDest);
|
||||
g_nancy->_sound->playSound(_minusSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int)_curPower < _powers - 1 && _plusButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_curPower;
|
||||
_drawSurface.blitFrom(_image, _plusButtonSrc, _plusButtonDest);
|
||||
g_nancy->_sound->playSound(_plusSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shootButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _shootButtonSrc, _shootButtonDest);
|
||||
g_nancy->_sound->playSound(_shootSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
98
engines/nancy/action/puzzle/bballpuzzle.h
Normal file
98
engines/nancy/action/puzzle/bballpuzzle.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BBALLPUZZLE_H
|
||||
#define NANCY_ACTION_BBALLPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class BBallPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BBallPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BBallPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "BBallPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _positions = 0;
|
||||
uint16 _powers = 0;
|
||||
uint16 _angles = 0;
|
||||
|
||||
Common::Array<Common::Point> _correctVals;
|
||||
|
||||
Common::Rect _shootButtonDest;
|
||||
Common::Rect _minusButtonDest;
|
||||
Common::Rect _plusButtonDest;
|
||||
|
||||
Common::Rect _playerDest;
|
||||
Common::Rect _powerDest;
|
||||
Common::Rect _angleDest;
|
||||
Common::Array<Common::Rect> _angleSliderHotspots;
|
||||
|
||||
Common::Rect _shootButtonSrc;
|
||||
Common::Rect _minusButtonSrc;
|
||||
Common::Rect _plusButtonSrc;
|
||||
|
||||
Common::Array<Common::Rect> _playerSrcs;
|
||||
Common::Array<Common::Rect> _powerSrcs;
|
||||
Common::Array<Common::Rect> _anglesSrcs;
|
||||
|
||||
SoundDescription _shootSound;
|
||||
SoundDescription _minusSound;
|
||||
SoundDescription _plusSound;
|
||||
|
||||
SceneChangeDescription _shootSceneChange;
|
||||
|
||||
Common::Array<int16> _badShootFlags;
|
||||
int16 _goodShootFlag = 0;
|
||||
Common::Array<int16> _playerPositionFlags;
|
||||
|
||||
int16 _winFlag = 0;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
uint _curAngle = 0;
|
||||
uint _curPower = 0;
|
||||
uint _curPosition = 0;
|
||||
|
||||
bool _pressedButton = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BBALLPUZZLE_H
|
||||
279
engines/nancy/action/puzzle/bombpuzzle.cpp
Normal file
279
engines/nancy/action/puzzle/bombpuzzle.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bombpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BombPuzzle::init() {
|
||||
_screenPosition = _displayBounds;
|
||||
for (Common::Rect &r : _wireDests) {
|
||||
_screenPosition.extend(r);
|
||||
}
|
||||
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
RenderActionRecord::init();
|
||||
}
|
||||
|
||||
void BombPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRectArray(stream, _wireSrcs, 4);
|
||||
readRectArray(stream, _wireDests, 4);
|
||||
readRectArray(stream, _digitSrcs, 10);
|
||||
readRectArray(stream, _digitDests, 4);
|
||||
readRect(stream, _colonSrc);
|
||||
readRect(stream, _colonDest);
|
||||
readRect(stream, _displayBounds);
|
||||
|
||||
_solveOrder.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_solveOrder[i] = stream.readByte();
|
||||
}
|
||||
|
||||
_snipSound.readNormal(stream);
|
||||
_noToolSound.readNormal(stream);
|
||||
_toolID = stream.readUint16LE();
|
||||
|
||||
_solveSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_failSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_failSound.readNormal(stream);
|
||||
|
||||
switch (NancySceneState.getDifficulty()) {
|
||||
case 0:
|
||||
_timerTotalTime = 30 * 1000;
|
||||
break;
|
||||
case 1:
|
||||
_timerTotalTime = 25 * 1000;
|
||||
break;
|
||||
case 2:
|
||||
_timerTotalTime = 20 * 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
_nextBlinkTime = _timerTotalTime;
|
||||
_timerBlinkTime = 10 * 1000; // 10 seconds for all difficulties
|
||||
}
|
||||
|
||||
void BombPuzzle::updateGraphics() {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Time timeRemaining = NancySceneState.getTimerTime();
|
||||
|
||||
if (timeRemaining == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeRemaining > _timerTotalTime) {
|
||||
timeRemaining = 0;
|
||||
} else {
|
||||
timeRemaining = _timerTotalTime - timeRemaining;
|
||||
}
|
||||
|
||||
bool toggleBlink = false;
|
||||
|
||||
if (timeRemaining < _nextBlinkTime) {
|
||||
_nextBlinkTime = timeRemaining - 300; // hardcoded to 300 ms
|
||||
toggleBlink = timeRemaining < _timerBlinkTime;
|
||||
}
|
||||
|
||||
if (_lastDrawnTime == timeRemaining.getSeconds() && !toggleBlink) {
|
||||
// State is the same as last call, do not redraw
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect t = _displayBounds;
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_lastDrawnTime = timeRemaining.getSeconds();
|
||||
|
||||
// Clear the display
|
||||
_drawSurface.fillRect(t, _drawSurface.getTransparentColor());
|
||||
|
||||
if (toggleBlink) {
|
||||
if (!_isBlinking) {
|
||||
// Only clear the display
|
||||
_isBlinking = true;
|
||||
_needsRedraw = true;
|
||||
return;
|
||||
} else {
|
||||
// Redraw the display
|
||||
_isBlinking = false;
|
||||
}
|
||||
} else {
|
||||
if (_isBlinking) {
|
||||
// Only clear the display
|
||||
_needsRedraw = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add 1 second to timer so it starts at 30/25/20 seconds
|
||||
timeRemaining += 1000;
|
||||
|
||||
// Draw 10s of minutes
|
||||
t = _digitDests[0];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getMinutes() / 10], t);
|
||||
|
||||
// Draw 1s of minutes
|
||||
t = _digitDests[1];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getMinutes() % 10], t);
|
||||
|
||||
// Draw 10s of seconds
|
||||
t = _digitDests[2];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getSeconds() / 10], t);
|
||||
|
||||
// Draw 1s of seconds
|
||||
t = _digitDests[3];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getSeconds() % 10], t);
|
||||
|
||||
// Draw colon
|
||||
t = _colonDest;
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _colonSrc, t);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void BombPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_snipSound);
|
||||
g_nancy->_sound->loadSound(_noToolSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
break;
|
||||
case kRun: {
|
||||
bool fail = false;
|
||||
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_playerOrder[i] != _solveOrder[i]) {
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
_failed = true;
|
||||
_state = kActionTrigger;
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerOrder.size() == _solveOrder.size()) {
|
||||
_failed = false;
|
||||
_state = kActionTrigger;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kActionTrigger:
|
||||
if (_failed) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
_failSceneChange.execute();
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_solveSceneChange.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_snipSound);
|
||||
g_nancy->_sound->stopSound(_noToolSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void BombPuzzle::handleInput(NancyInput &input) {
|
||||
for (uint i = 0 ; i < _wireDests.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_wireDests[i]).contains(input.mousePos)) {
|
||||
for (byte j : _playerOrder) {
|
||||
if (i == j) {
|
||||
// Wire already snipped, do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (NancySceneState.getHeldItem() == _toolID) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_snipSound)) {
|
||||
_playerOrder.push_back(i);
|
||||
g_nancy->_sound->playSound(_snipSound);
|
||||
Common::Rect dest = _wireDests[i];
|
||||
dest.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _wireSrcs[i], dest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_noToolSound);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
78
engines/nancy/action/puzzle/bombpuzzle.h
Normal file
78
engines/nancy/action/puzzle/bombpuzzle.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BOMBPUZZLE_H
|
||||
#define NANCY_ACTION_BOMBPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class BombPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BombPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BombPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "BombPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _wireSrcs;
|
||||
Common::Array<Common::Rect> _wireDests;
|
||||
Common::Array<Common::Rect> _digitSrcs;
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Rect _colonSrc;
|
||||
Common::Rect _colonDest;
|
||||
Common::Rect _displayBounds;
|
||||
Common::Array<byte> _solveOrder;
|
||||
SoundDescription _snipSound;
|
||||
SoundDescription _noToolSound;
|
||||
uint16 _toolID = 0;
|
||||
SceneChangeWithFlag _solveSceneChange;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _failSceneChange;
|
||||
SoundDescription _failSound;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<byte> _playerOrder;
|
||||
|
||||
Time _timerTotalTime;
|
||||
Time _timerBlinkTime;
|
||||
Time _nextBlinkTime;
|
||||
bool _isBlinking = false;
|
||||
uint _lastDrawnTime = 0;
|
||||
|
||||
bool _failed = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BOMBPUZZLE_H
|
||||
463
engines/nancy/action/puzzle/bulpuzzle.cpp
Normal file
463
engines/nancy/action/puzzle/bulpuzzle.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bulpuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BulPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
reset(false);
|
||||
|
||||
for (int i = 0; i < _numPieces - 1; ++i) {
|
||||
_drawSurface.blitFrom(_image, _playerBarracksSrc, _playerBarracksDests[i]);
|
||||
_drawSurface.blitFrom(_image, _enemyBarracksSrc, _enemyBarracksDests[i]);
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
}
|
||||
|
||||
void BulPuzzle::updateGraphics() {
|
||||
bool isPlayer = _turn / _numRolls == 0;
|
||||
|
||||
if (_currentAction == kCapture && g_nancy->getTotalPlayTime() > _nextMoveTime) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_playerCapturedSound) || g_nancy->_sound->isSoundPlaying(_enemyCapturedSound)) {
|
||||
return;
|
||||
} else {
|
||||
if (isPlayer) {
|
||||
--_enemyPieces;
|
||||
} else {
|
||||
--_playerPieces;
|
||||
}
|
||||
|
||||
if (_playerPieces && _enemyPieces) {
|
||||
reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_changeLight && !g_nancy->_sound->isSoundPlaying(_moveSound)) {
|
||||
if (_turn == 0) {
|
||||
_drawSurface.fillRect(_enemyLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
} else if (_turn == _numRolls) {
|
||||
_drawSurface.fillRect(_playerLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _enemyLightSrc, _enemyLightDest);
|
||||
}
|
||||
|
||||
if (_turn == 0 || _turn == _numRolls) {
|
||||
_drawSurface.blitFrom(_image, _passButtonDisabledSrc, _passButtonDest);
|
||||
} else {
|
||||
_drawSurface.fillRect(_passButtonDest, _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_changeLight = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_nextMoveTime && g_nancy->getTotalPlayTime() > _nextMoveTime) {
|
||||
// First, handle buttons
|
||||
if (_pushedButton) {
|
||||
switch (_currentAction) {
|
||||
case kRoll:
|
||||
_drawSurface.fillRect(_rollButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
// Do the roll logic here since it's more convenient
|
||||
for (uint i = 0; i < _diceDestsPlayer.size(); ++i) {
|
||||
Common::Rect *dest = isPlayer ? &_diceDestsPlayer[i] : &_diceDestsEnemy[i];
|
||||
_drawSurface.fillRect(*dest, _drawSurface.getTransparentColor());
|
||||
bool black = g_nancy->_randomSource->getRandomBit();
|
||||
if (black) {
|
||||
// Black, add one movement
|
||||
_drawSurface.blitFrom(_image, _diceBlackSrcs[g_nancy->_randomSource->getRandomNumber(_diceBlackSrcs.size() - 1)], *dest);
|
||||
++_moveDiff;
|
||||
} else {
|
||||
// Non-black, no movement
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[g_nancy->_randomSource->getRandomNumber(_diceCleanSrcs.size() - 1)], *dest);
|
||||
}
|
||||
}
|
||||
|
||||
if (_moveDiff == 0) {
|
||||
_moveDiff = 5;
|
||||
}
|
||||
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 200;
|
||||
break;
|
||||
case kPass:
|
||||
_drawSurface.fillRect(_passButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (isPlayer) {
|
||||
_drawSurface.fillRect(_playerLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _enemyLightSrc, _enemyLightDest);
|
||||
_turn = _numRolls;
|
||||
} else {
|
||||
_drawSurface.fillRect(_enemyLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
_turn = 0;
|
||||
}
|
||||
|
||||
_currentAction = kNone;
|
||||
_nextMoveTime = 0;
|
||||
break;
|
||||
case kReset:
|
||||
_drawSurface.fillRect(_resetButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_cellDests[_playerPos], _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_cellDests[_enemyPos], _drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _playerJailDests.size(); ++i) {
|
||||
_drawSurface.fillRect(_playerJailDests[i], _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_enemyJailDests[i], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_pushedButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_rollSound) ||
|
||||
g_nancy->_sound->isSoundPlaying(_passSound) ||
|
||||
g_nancy->_sound->isSoundPlaying(_resetSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, handle the movement logic
|
||||
switch (_currentAction) {
|
||||
case kRoll:
|
||||
if (_moveDiff) {
|
||||
// Moving
|
||||
movePiece(isPlayer);
|
||||
--_moveDiff;
|
||||
|
||||
if (_moveDiff || _playerPos == _enemyPos) {
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 200; // hardcoded
|
||||
} else {
|
||||
// This was the last move, go to next turn
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_currentAction = kNone;
|
||||
_turn = _turn + 1 > 3 ? 0 : _turn + 1;
|
||||
_changeLight = true;
|
||||
}
|
||||
} else {
|
||||
// Capturing
|
||||
SoundDescription &sound = isPlayer ? _enemyCapturedSound : _playerCapturedSound;
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
_drawSurface.fillRect(_cellDests[_playerPos], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, isPlayer ? _enemyCapturedSrc : _playerCapturedSrc, _cellDests[_playerPos]);
|
||||
_currentAction = kCapture;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 1000;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
case kPass:
|
||||
_currentAction = kNone;
|
||||
_turn = (_turn + 1 > _numRolls * 2) ? 0 : _turn + 1;
|
||||
|
||||
return;
|
||||
case kReset:
|
||||
reset(false);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numCells = stream.readUint16LE();
|
||||
_numPieces = stream.readUint16LE();
|
||||
_numRolls = stream.readUint16LE();
|
||||
_playerStart = stream.readUint16LE();
|
||||
_enemyStart = stream.readUint16LE();
|
||||
|
||||
readRectArray(stream, _diceDestsPlayer, 4);
|
||||
readRectArray(stream, _diceDestsEnemy, 4);
|
||||
|
||||
readRectArray(stream, _cellDests, _numCells, 15);
|
||||
|
||||
readRectArray(stream, _playerBarracksDests, 6);
|
||||
readRectArray(stream, _playerJailDests, 6);
|
||||
readRectArray(stream, _enemyBarracksDests, 6);
|
||||
readRectArray(stream, _enemyJailDests, 6);
|
||||
|
||||
readRect(stream, _rollButtonDest);
|
||||
readRect(stream, _passButtonDest);
|
||||
readRect(stream, _resetButtonDest);
|
||||
readRect(stream, _playerLightDest);
|
||||
readRect(stream, _enemyLightDest);
|
||||
|
||||
_diceBlackSrcs.resize(4);
|
||||
_diceCleanSrcs.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
readRect(stream, _diceCleanSrcs[i]);
|
||||
readRect(stream, _diceBlackSrcs[i]);
|
||||
}
|
||||
|
||||
readRect(stream, _playerSrc);
|
||||
readRect(stream, _enemySrc);
|
||||
readRect(stream, _enemyCapturedSrc);
|
||||
readRect(stream, _playerCapturedSrc);
|
||||
|
||||
readRect(stream, _playerBarracksSrc);
|
||||
readRect(stream, _enemyBarracksSrc);
|
||||
readRect(stream, _playerJailSrc);
|
||||
readRect(stream, _enemyJailSrc);
|
||||
|
||||
readRect(stream, _rollButtonSrc);
|
||||
readRect(stream, _passButtonSrc);
|
||||
readRect(stream, _resetButtonSrc);
|
||||
readRect(stream, _playerLightSrc);
|
||||
readRect(stream, _enemyLightSrc);
|
||||
readRect(stream, _passButtonDisabledSrc);
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
_enemyCapturedSound.readNormal(stream);
|
||||
_playerCapturedSound.readNormal(stream);
|
||||
_rollSound.readNormal(stream);
|
||||
_passSound.readNormal(stream);
|
||||
_resetSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
_loseSoundDelay = stream.readUint16LE();
|
||||
_loseSound.readNormal(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void BulPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rollSound);
|
||||
g_nancy->_sound->loadSound(_resetSound);
|
||||
g_nancy->_sound->loadSound(_passSound);
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_playerPieces == 0) {
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + _loseSoundDelay * 1000;
|
||||
}
|
||||
|
||||
if (_enemyPieces == 0) {
|
||||
_playerWon = true;
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
}
|
||||
|
||||
if (_state == kRun) {
|
||||
break;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kActionTrigger:
|
||||
SoundDescription &sound = _playerWon ? _solveSound : _loseSound;
|
||||
|
||||
if (g_nancy->getTotalPlayTime() >= _nextMoveTime) {
|
||||
_nextMoveTime = 0;
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
}
|
||||
|
||||
if (_nextMoveTime == 0 && !g_nancy->_sound->isSoundPlaying(sound)) {
|
||||
if (_playerWon) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::handleInput(NancyInput &input) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pushedButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool canClick = _currentAction == kNone && !g_nancy->_sound->isSoundPlaying(_moveSound);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_rollButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _rollButtonSrc, _rollButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_rollSound);
|
||||
_currentAction = kRoll;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_turn % _numRolls) && NancySceneState.getViewport().convertViewportToScreen(_passButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _passButtonSrc, _passButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_passSound);
|
||||
_currentAction = kPass;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_resetButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _resetButtonSrc, _resetButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_resetSound);
|
||||
_currentAction = kReset;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::movePiece(bool player) {
|
||||
int16 &piecePos = player ? _playerPos : _enemyPos;
|
||||
_drawSurface.fillRect(_cellDests[piecePos], _drawSurface.getTransparentColor());
|
||||
piecePos += player ? 1 : -1;
|
||||
|
||||
if (ABS<int16>(_playerPos - _enemyPos) == 1) {
|
||||
// Redraw other piece in case one piece goes behind the other's back
|
||||
_drawSurface.blitFrom(_image, player ? _enemySrc : _playerSrc, _cellDests[player ? _enemyPos : _playerPos]);
|
||||
}
|
||||
|
||||
if (piecePos < 0) {
|
||||
piecePos = _cellDests.size() - 1;
|
||||
} else if (piecePos > (int)_cellDests.size() - 1) {
|
||||
piecePos = 0;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, player ? _playerSrc : _enemySrc, _cellDests[piecePos]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void BulPuzzle::reset(bool capture) {
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
|
||||
// Reset dice
|
||||
for (uint i = 0; i < _diceDestsPlayer.size(); ++i) {
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[i], _diceDestsPlayer[i]);
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[i], _diceDestsEnemy[i]);
|
||||
}
|
||||
|
||||
if (!capture) {
|
||||
_playerPieces = _enemyPieces = _numPieces;
|
||||
}
|
||||
|
||||
// Reset player/enemy
|
||||
_playerPos = _playerStart - 1;
|
||||
_enemyPos = _enemyStart - 1;
|
||||
_drawSurface.blitFrom(_image, _playerSrc, _cellDests[_playerPos]);
|
||||
_drawSurface.blitFrom(_image, _enemySrc, _cellDests[_enemyPos]);
|
||||
|
||||
// Reset to player turn
|
||||
_turn = 0;
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
|
||||
// Draw jail and barracks
|
||||
for (int i = 0; i < _numPieces - 1; ++i) {
|
||||
if (i < _playerPieces - 1) {
|
||||
// Draw piece in barracks
|
||||
_drawSurface.blitFrom(_image, _playerBarracksSrc, _playerBarracksDests[i]);
|
||||
} else {
|
||||
// Draw piece in jail
|
||||
_drawSurface.blitFrom(_image, _playerJailSrc, _enemyJailDests[i - _playerPieces + 1]);
|
||||
}
|
||||
|
||||
if (i < _enemyPieces - 1) {
|
||||
// Draw piece in barracks
|
||||
_drawSurface.blitFrom(_image, _enemyBarracksSrc, _enemyBarracksDests[i]);
|
||||
} else {
|
||||
// Draw piece in jail
|
||||
_drawSurface.blitFrom(_image, _enemyJailSrc, _playerJailDests[i - _enemyPieces + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw disabled pass button
|
||||
_drawSurface.blitFrom(_image, _passButtonDisabledSrc, _passButtonDest);
|
||||
|
||||
_currentAction = kNone;
|
||||
_nextMoveTime = 0;
|
||||
_pushedButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
134
engines/nancy/action/puzzle/bulpuzzle.h
Normal file
134
engines/nancy/action/puzzle/bulpuzzle.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BULPUZZLE_H
|
||||
#define NANCY_ACTION_BULPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle based around a simplified version of the Mayan game Bul
|
||||
class BulPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BulPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BulPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum BulAction { kNone, kRoll, kPass, kReset, kCapture };
|
||||
|
||||
void movePiece(bool player);
|
||||
void reset(bool capture);
|
||||
|
||||
Common::String getRecordTypeName() const override { return "BulPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numCells = 0;
|
||||
uint16 _numPieces = 0;
|
||||
uint16 _numRolls = 0;
|
||||
|
||||
uint16 _playerStart = 0;
|
||||
uint16 _enemyStart = 0;
|
||||
|
||||
Common::Array<Common::Rect> _diceDestsPlayer;
|
||||
Common::Array<Common::Rect> _diceDestsEnemy;
|
||||
|
||||
Common::Array<Common::Rect> _cellDests;
|
||||
|
||||
Common::Array<Common::Rect> _playerBarracksDests;
|
||||
Common::Array<Common::Rect> _playerJailDests;
|
||||
|
||||
Common::Array<Common::Rect> _enemyBarracksDests;
|
||||
Common::Array<Common::Rect> _enemyJailDests;
|
||||
|
||||
Common::Rect _rollButtonDest;
|
||||
Common::Rect _passButtonDest;
|
||||
Common::Rect _resetButtonDest;
|
||||
Common::Rect _playerLightDest;
|
||||
Common::Rect _enemyLightDest;
|
||||
|
||||
Common::Array<Common::Rect> _diceBlackSrcs;
|
||||
Common::Array<Common::Rect> _diceCleanSrcs;
|
||||
|
||||
Common::Rect _playerSrc;
|
||||
Common::Rect _enemySrc;
|
||||
Common::Rect _playerCapturedSrc;
|
||||
Common::Rect _enemyCapturedSrc;
|
||||
|
||||
Common::Rect _playerBarracksSrc;
|
||||
Common::Rect _enemyBarracksSrc;
|
||||
Common::Rect _playerJailSrc;
|
||||
Common::Rect _enemyJailSrc;
|
||||
|
||||
Common::Rect _rollButtonSrc;
|
||||
Common::Rect _passButtonSrc;
|
||||
Common::Rect _passButtonDisabledSrc;
|
||||
Common::Rect _resetButtonSrc;
|
||||
Common::Rect _playerLightSrc;
|
||||
Common::Rect _enemyLightSrc;
|
||||
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _playerCapturedSound;
|
||||
SoundDescription _enemyCapturedSound;
|
||||
SoundDescription _rollSound;
|
||||
SoundDescription _passSound;
|
||||
SoundDescription _resetSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene; // also when losing
|
||||
uint16 _loseSoundDelay = 0;
|
||||
SoundDescription _loseSound;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int16 _playerPos = 0;
|
||||
int16 _playerPieces = 0;
|
||||
int16 _enemyPos = 0;
|
||||
int16 _enemyPieces = 0;
|
||||
|
||||
uint16 _turn = 0;
|
||||
uint16 _moveDiff = 0;
|
||||
uint32 _nextMoveTime = 0;
|
||||
bool _pushedButton = false;
|
||||
bool _changeLight = false;
|
||||
BulAction _currentAction = kNone;
|
||||
|
||||
bool _playerWon = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BULPUZZLE_H
|
||||
703
engines/nancy/action/puzzle/collisionpuzzle.cpp
Normal file
703
engines/nancy/action/puzzle/collisionpuzzle.cpp
Normal file
@@ -0,0 +1,703 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/collisionpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CollisionPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
_pieces.resize(_pieceSrcs.size(), Piece());
|
||||
for (uint i = 0; i < _pieceSrcs.size(); ++i) {
|
||||
_pieces[i]._drawSurface.create(_image, _pieceSrcs[i]);
|
||||
Common::Rect pos = getScreenPosition(_startLocations[i]);
|
||||
if (_lineWidth == 6) {
|
||||
pos.translate(-1, 0); // Improvement
|
||||
}
|
||||
_pieces[i].moveTo(pos);
|
||||
_pieces[i]._gridPos = _startLocations[i];
|
||||
_pieces[i].setVisible(true);
|
||||
_pieces[i].setTransparent(true);
|
||||
}
|
||||
} else {
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[0].size(); ++x) {
|
||||
if (_grid[y][x] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Piece newPiece;
|
||||
uint id = _grid[y][x];
|
||||
|
||||
switch (id) {
|
||||
case 1 :
|
||||
newPiece._w = 2;
|
||||
break;
|
||||
case 2 :
|
||||
newPiece._h = 2;
|
||||
break;
|
||||
case 3 :
|
||||
newPiece._w = 3;
|
||||
break;
|
||||
case 4 :
|
||||
newPiece._h = 3;
|
||||
break;
|
||||
case 5 :
|
||||
newPiece._w = 2;
|
||||
newPiece._h = 2;
|
||||
break;
|
||||
case 6 :
|
||||
newPiece._w = 2;
|
||||
break;
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
|
||||
newPiece._drawSurface.create(_image, _pieceSrcs[id - 1]);
|
||||
Common::Rect pos = getScreenPosition(Common::Point(x, y));
|
||||
if (_lineWidth == 6) {
|
||||
pos.translate(-1, 0); // Improvement
|
||||
}
|
||||
pos.setWidth(newPiece._drawSurface.w);
|
||||
pos.setHeight(newPiece._drawSurface.h);
|
||||
newPiece.moveTo(pos);
|
||||
newPiece._gridPos = Common::Point(x, y);
|
||||
newPiece.setVisible(true);
|
||||
newPiece.setTransparent(true);
|
||||
|
||||
if (id == 6) {
|
||||
// The solve piece is pushed to the front
|
||||
_pieces.insert_at(0, newPiece);
|
||||
} else {
|
||||
_pieces.push_back(newPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void CollisionPuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void CollisionPuzzle::updateGraphics() {
|
||||
if (_state == kRun) {
|
||||
if (_timerSrcs.size()) {
|
||||
uint32 currentTime = g_nancy->getTotalPlayTime() - _puzzleStartTime;
|
||||
int graphicForTime = currentTime / ((_timerTime * 1000) / _timerSrcs.size());
|
||||
if (graphicForTime != _currentTimerGraphic) {
|
||||
_drawSurface.fillRect(_timerDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _timerSrcs[graphicForTime], _timerDest);
|
||||
_needsRedraw = true;
|
||||
_currentTimerGraphic = graphicForTime;
|
||||
NancySceneState.setEventFlag(_timerFlagIds[graphicForTime], g_nancy->_true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentlyAnimating != -1) {
|
||||
// Framerate-dependent animation. Should be fine since we limit the engine to ~60fps
|
||||
++_currentAnimFrame;
|
||||
bool horizontal = _lastPosition.x != _pieces[_currentlyAnimating]._gridPos.x;
|
||||
int diff = horizontal ?
|
||||
_lastPosition.x - _pieces[_currentlyAnimating]._gridPos.x :
|
||||
_lastPosition.y - _pieces[_currentlyAnimating]._gridPos.y;
|
||||
|
||||
int maxFrames = _framesPerMove * abs(diff);
|
||||
if (_currentAnimFrame > maxFrames) {
|
||||
if (_puzzleType == kCollision && _grid[_pieces[_currentlyAnimating]._gridPos.y][_pieces[_currentlyAnimating]._gridPos.x] == _currentlyAnimating + 1) {
|
||||
g_nancy->_sound->playSound(_homeSound);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_wallHitSound);
|
||||
}
|
||||
|
||||
_currentlyAnimating = -1;
|
||||
_currentAnimFrame = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect destRect = getScreenPosition(_lastPosition);
|
||||
Common::Rect endPos = getScreenPosition(_pieces[_currentlyAnimating]._gridPos);
|
||||
|
||||
if (_lineWidth == 6) {
|
||||
destRect.translate(-1, 0); // Improvement
|
||||
endPos.translate(-1, 0); // Improvement
|
||||
}
|
||||
|
||||
Common::Point dest(destRect.left, destRect.top);
|
||||
if (horizontal) {
|
||||
dest.x = destRect.left + (endPos.left - dest.x) * _currentAnimFrame / maxFrames;
|
||||
} else {
|
||||
dest.y = destRect.top + (endPos.top - dest.y) * _currentAnimFrame / maxFrames;
|
||||
}
|
||||
|
||||
_pieces[_currentlyAnimating].moveTo(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
uint16 numPieces = 0;
|
||||
|
||||
uint16 width = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE();
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
numPieces = stream.readUint16LE();
|
||||
} else {
|
||||
_tileMoveExitPos.y = stream.readUint16LE();
|
||||
_tileMoveExitPos.x = stream.readUint16LE();
|
||||
_tileMoveExitSize = stream.readUint16LE();
|
||||
numPieces = 6;
|
||||
}
|
||||
|
||||
_grid.resize(height, Common::Array<uint16>(width));
|
||||
for (uint y = 0; y < height; ++y) {
|
||||
for (uint x = 0; x < width; ++x) {
|
||||
_grid[y][x] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - width) * 2);
|
||||
}
|
||||
stream.skip((8 - height) * 8 * 2);
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
_startLocations.resize(numPieces);
|
||||
for (uint i = 0; i < numPieces; ++i) {
|
||||
_startLocations[i].x = stream.readUint16LE();
|
||||
_startLocations[i].y = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((5 - numPieces) * 4);
|
||||
|
||||
readRectArray(stream, _pieceSrcs, numPieces, 5);
|
||||
readRectArray(stream, _homeSrcs, numPieces, 5);
|
||||
|
||||
readRect(stream, _verticalWallSrc);
|
||||
readRect(stream, _horizontalWallSrc);
|
||||
readRect(stream, _blockSrc);
|
||||
} else {
|
||||
readRectArray(stream, _pieceSrcs, 6);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_usesExitButton = stream.readByte();
|
||||
readRect(stream, _exitButtonSrc);
|
||||
readRect(stream, _exitButtonDest);
|
||||
}
|
||||
}
|
||||
|
||||
_gridPos.x = stream.readUint32LE();
|
||||
_gridPos.y = stream.readUint32LE();
|
||||
|
||||
_lineWidth = stream.readUint16LE();
|
||||
_framesPerMove = stream.readUint16LE();
|
||||
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy7) {
|
||||
stream.skip(3);
|
||||
} else if (_puzzleType == kTileMove) {
|
||||
uint16 numTimerGraphics = stream.readUint16LE();
|
||||
_timerTime = stream.readUint32LE();
|
||||
readRectArray(stream, _timerSrcs, numTimerGraphics, 10);
|
||||
_timerFlagIds.resize(numTimerGraphics);
|
||||
for (uint i = 0; i < numTimerGraphics; ++i) {
|
||||
_timerFlagIds[i] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((10 - numTimerGraphics) * 2);
|
||||
readRect(stream, _timerDest);
|
||||
}
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
if (_puzzleType == kCollision) {
|
||||
_homeSound.readNormal(stream);
|
||||
}
|
||||
_wallHitSound.readNormal(stream);
|
||||
if (_puzzleType == kTileMove && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_exitButtonSound.readNormal(stream);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void CollisionPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_wallHitSound);
|
||||
g_nancy->_sound->loadSound(_homeSound);
|
||||
NancySceneState.setNoHeldItem();
|
||||
_puzzleStartTime = g_nancy->getTotalPlayTime();
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_currentlyAnimating != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check timer
|
||||
if (_timerSrcs.size()) {
|
||||
if ((g_nancy->getTotalPlayTime() - _puzzleStartTime) > _timerTime * 1000) {
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
// Check if every tile is in its "home"
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_grid[_pieces[i]._gridPos.y][_pieces[i]._gridPos.x] != i + 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check if either:
|
||||
// - the solve tile is over the exit or;
|
||||
// - the solve tile is outside the bounds of the grid (and is thus inside the exit)
|
||||
Common::Point pos = _pieces[0]._gridPos;
|
||||
Common::Rect posRect(pos.x, pos.y, pos.x + _pieces[0]._w, pos.y + _pieces[0]._h);
|
||||
Common::Rect gridRect(_grid.size(), _grid[0].size());
|
||||
if (!posRect.contains(_tileMoveExitPos) && gridRect.contains(pos)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_state = kActionTrigger;
|
||||
_solved = true;
|
||||
return;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (_solveSoundPlayTime != 0) {
|
||||
if (g_nancy->getTotalPlayTime() < _solveSoundPlayTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_solveSoundPlayTime = 0;
|
||||
return;
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
}
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_exitButtonSound)) {
|
||||
return;
|
||||
}
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_wallHitSound);
|
||||
g_nancy->_sound->stopSound(_homeSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
|
||||
Common::Point newPos = _pieces[pieceID]._gridPos;
|
||||
bool done = false;
|
||||
|
||||
uint preStopWallType = 0;
|
||||
uint postStopWallType = 0;
|
||||
int inc = 0;
|
||||
bool horizontal = false;
|
||||
|
||||
switch (direction) {
|
||||
case kWallLeft :
|
||||
preStopWallType = kWallRight;
|
||||
postStopWallType = kWallLeft;
|
||||
inc = -1;
|
||||
horizontal = true;
|
||||
|
||||
break;
|
||||
case kWallRight :
|
||||
preStopWallType = kWallLeft;
|
||||
postStopWallType = kWallRight;
|
||||
inc = 1;
|
||||
horizontal = true;
|
||||
|
||||
break;
|
||||
case kWallUp :
|
||||
preStopWallType = kWallDown;
|
||||
postStopWallType = kWallUp;
|
||||
inc = -1;
|
||||
horizontal = false;
|
||||
|
||||
break;
|
||||
case kWallDown :
|
||||
preStopWallType = kWallUp;
|
||||
postStopWallType = kWallDown;
|
||||
inc = 1;
|
||||
horizontal = false;
|
||||
|
||||
break;
|
||||
default:
|
||||
return { -1, -1 };
|
||||
}
|
||||
|
||||
// Set the last possible position to check before the piece would be out of bounds
|
||||
int lastPos = inc > 0 ? (horizontal ? (int)_grid[0].size() : (int)_grid.size()) : -1;
|
||||
if (lastPos != -1) {
|
||||
// For TileMove, ensure wider pieces won't clip out
|
||||
lastPos -= inc * ((horizontal ? _pieces[pieceID]._w : _pieces[pieceID]._h) - 1);
|
||||
}
|
||||
|
||||
for (int i = (horizontal ? newPos.x : newPos.y) + inc; (inc > 0 ? i < lastPos : i > lastPos); i += inc) {
|
||||
// First, check if other pieces would block
|
||||
Common::Point comparePos = newPos;
|
||||
if (horizontal) {
|
||||
comparePos.x = i;
|
||||
} else {
|
||||
comparePos.y = i;
|
||||
}
|
||||
|
||||
Common::Rect compareRect(comparePos.x, comparePos.y, comparePos.x + _pieces[pieceID]._w, comparePos.y + _pieces[pieceID]._h);
|
||||
|
||||
for (uint j = 0; j < _pieces.size(); ++j) {
|
||||
if (pieceID == j) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Common::Rect pieceBounds( _pieces[j]._gridPos.x,
|
||||
_pieces[j]._gridPos.y,
|
||||
_pieces[j]._gridPos.x + _pieces[j]._w,
|
||||
_pieces[j]._gridPos.y + _pieces[j]._h);
|
||||
if (pieceBounds.intersects(compareRect)) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
// Next, check the grid for blocking walls
|
||||
uint16 evalVal = horizontal ? _grid[newPos.y][i] : _grid[i][newPos.x];
|
||||
if (evalVal == postStopWallType) {
|
||||
if (horizontal) {
|
||||
newPos.x = i;
|
||||
} else {
|
||||
newPos.y = i;
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (evalVal == preStopWallType || evalVal == kBlock) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (horizontal) {
|
||||
newPos.x = i;
|
||||
} else {
|
||||
newPos.y = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Move result outside of grid when the exit is at an edge, and the moved piece is on top of the exit
|
||||
if (_puzzleType == kTileMove && pieceID == 0) {
|
||||
Common::Rect compareRect(newPos.x, newPos.y, newPos.x + _pieces[pieceID]._w, newPos.y + _pieces[pieceID]._h);
|
||||
if (compareRect.contains(_tileMoveExitPos)) {
|
||||
if (horizontal && (_tileMoveExitPos.x == 0 || _tileMoveExitPos.x == (int)_grid[0].size() - 1)) {
|
||||
newPos.x += inc * _tileMoveExitSize;
|
||||
} else if (!horizontal && (_tileMoveExitPos.y == 0 || _tileMoveExitPos.y == (int)_grid.size() - 1)) {
|
||||
newPos.y += inc * _tileMoveExitSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newPos;
|
||||
}
|
||||
|
||||
Common::Rect CollisionPuzzle::getScreenPosition(Common::Point gridPos) {
|
||||
Common::Rect dest = _pieceSrcs[0];
|
||||
|
||||
dest.moveTo(0, 0);
|
||||
|
||||
dest.right -= 1;
|
||||
dest.bottom -= 1;
|
||||
|
||||
if (_puzzleType == kTileMove) {
|
||||
dest.setWidth(dest.width() / 2);
|
||||
}
|
||||
|
||||
dest.moveTo(_gridPos);
|
||||
dest.translate(gridPos.x * dest.width(), gridPos.y *dest.height());
|
||||
dest.translate(gridPos.x * _lineWidth, gridPos.y * _lineWidth);
|
||||
|
||||
dest.right += 1;
|
||||
dest.bottom += 1;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void CollisionPuzzle::drawGrid() {
|
||||
// Improvement: original rendering does not line up with the grid on either difficulty, but ours does
|
||||
// The differences are marked below
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[y].size(); ++x) {
|
||||
uint16 cell = _grid[y][x];
|
||||
Common::Rect cellRect = getScreenPosition(Common::Point(x, y));
|
||||
Common::Point dest(cellRect.left, cellRect.top);
|
||||
|
||||
switch (cell) {
|
||||
case kBlock :
|
||||
|
||||
if (_lineWidth != 6) { // Improvement
|
||||
dest.x += 1;
|
||||
dest.y += 1;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _blockSrc, dest);
|
||||
break;
|
||||
case kWallLeft :
|
||||
dest.x -= _lineWidth - _lineWidth / 6;
|
||||
dest.y = cellRect.top + (cellRect.height() - _verticalWallSrc.height()) / 2;
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallRight :
|
||||
dest.x = cellRect.right - 1 + _lineWidth / 6;
|
||||
dest.y = cellRect.top + (cellRect.height() - _verticalWallSrc.height()) / 2;
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallUp :
|
||||
dest.x += (cellRect.width() - _horizontalWallSrc.width()) / 2;
|
||||
dest.y -= _lineWidth - _lineWidth / 6;
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallDown :
|
||||
dest.x += (cellRect.width() - _horizontalWallSrc.width()) / 2;
|
||||
dest.y = cellRect.bottom - 1 + _lineWidth / 6;
|
||||
|
||||
if (_lineWidth != 6) { // Improvement
|
||||
++dest.y;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest);
|
||||
|
||||
break;
|
||||
default :
|
||||
if (cell == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_lineWidth == 6) { // Improvement
|
||||
dest.x -= 1;
|
||||
} else {
|
||||
dest.x += 1;
|
||||
dest.y += 1;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _homeSrcs[cell - 1], dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void CollisionPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usesExitButton) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _exitButtonSrc, _exitButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->loadSound(_exitButtonSound);
|
||||
g_nancy->_sound->playSound(_exitButtonSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentlyAnimating != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Common::Point checkPos;
|
||||
Common::Rect left, right, up, down;
|
||||
Common::Rect screenPos = _pieces[i].getScreenPosition();
|
||||
|
||||
if (_pieces[i]._w == _pieces[i]._h) {
|
||||
// Width == height, all movement is permitted, hotspots are 10 pixels wide
|
||||
left.setWidth(10);
|
||||
left.setHeight(screenPos.height() - 20);
|
||||
left.moveTo(screenPos.left, screenPos.top + 10);
|
||||
right = left;
|
||||
right.translate(screenPos.width() - 10, 0);
|
||||
|
||||
up.setHeight(10);
|
||||
up.setWidth(screenPos.width() - 20);
|
||||
up.moveTo(screenPos.left + 10, screenPos.top);
|
||||
down = up;
|
||||
down.translate(0, screenPos.height() - 10);
|
||||
} else if (_pieces[i]._w > _pieces[i]._h) {
|
||||
// Width > height, only left/right movement is permitted, hotspots are the size of 1 cell
|
||||
left.setWidth(screenPos.width() / _pieces[i]._w);
|
||||
left.setHeight(screenPos.height() / _pieces[i]._h);
|
||||
left.moveTo(screenPos.left, screenPos.top);
|
||||
right = left;
|
||||
right.translate(right.width() * (_pieces[i]._w - 1), 0);
|
||||
} else {
|
||||
// Width < height, only up/down movement is permitted, hotspots are the size of 1 cell
|
||||
up.setWidth(screenPos.width() / _pieces[i]._w);
|
||||
up.setHeight(screenPos.height() / _pieces[i]._h);
|
||||
up.moveTo(screenPos.left, screenPos.top);
|
||||
down = up;
|
||||
down.translate(0, down.height() * (_pieces[i]._h - 1));
|
||||
}
|
||||
|
||||
if (!left.isEmpty()) {
|
||||
if (left.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallLeft);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveLeft);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!right.isEmpty()) {
|
||||
if (right.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallRight);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveRight);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!up.isEmpty()) {
|
||||
if (up.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallUp);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveUp);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!down.isEmpty()) {
|
||||
if (down.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallDown);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveDown);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
136
engines/nancy/action/puzzle/collisionpuzzle.h
Normal file
136
engines/nancy/action/puzzle/collisionpuzzle.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
#define NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Class responsible for two similar puzzle types, both of which have
|
||||
// rectangular tiles on a grid, which can move up/down/left/right until they
|
||||
// hit a wall or another tile
|
||||
// - CollisionPuzzle: Several 1x1 tiles, each of which has a "home" it needs to reach.
|
||||
// The grid contains walls. Tiles move in all directions.
|
||||
// - TileMovePuzzle: Many differently-sized tiles, one of which must reach the exit.
|
||||
// Rectangular tiles can only move in the directions parallel to their longer sides.
|
||||
// Exit is outside of the tile grid.
|
||||
class CollisionPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum PuzzleType { kCollision, kTileMove };
|
||||
CollisionPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
|
||||
virtual ~CollisionPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
// numbers 1-5 are home IDs, 0 is empty cell
|
||||
enum WallType { kWallLeft = 6, kWallUp = 7, kWallDown = 8, kWallRight = 9, kBlock = 10 };
|
||||
|
||||
class Piece : public RenderObject {
|
||||
public:
|
||||
Piece() : RenderObject(9) {}
|
||||
virtual ~Piece() {}
|
||||
|
||||
Common::Point _gridPos;
|
||||
uint _w = 1;
|
||||
uint _h = 1;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return _puzzleType == kCollision ? "CollisionPuzzle" : "TileMovePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Point movePiece(uint pieceID, WallType direction);
|
||||
Common::Rect getScreenPosition(Common::Point gridPos);
|
||||
void drawGrid();
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Array<Common::Array<uint16>> _grid;
|
||||
Common::Array<Common::Point> _startLocations;
|
||||
|
||||
Common::Array<Common::Rect> _pieceSrcs;
|
||||
Common::Array<Common::Rect> _homeSrcs;
|
||||
|
||||
Common::Rect _verticalWallSrc;
|
||||
Common::Rect _horizontalWallSrc;
|
||||
Common::Rect _blockSrc;
|
||||
|
||||
Common::Point _tileMoveExitPos = Common::Point(-1, -1);
|
||||
uint _tileMoveExitSize = 0;
|
||||
|
||||
bool _usesExitButton = false;
|
||||
Common::Rect _exitButtonSrc;
|
||||
Common::Rect _exitButtonDest;
|
||||
|
||||
Common::Point _gridPos;
|
||||
|
||||
uint16 _lineWidth = 0;
|
||||
uint16 _framesPerMove = 0;
|
||||
|
||||
uint32 _timerTime = 0; // in seconds
|
||||
Common::Array<Common::Rect> _timerSrcs;
|
||||
Common::Array<int16> _timerFlagIds;
|
||||
Common::Rect _timerDest;
|
||||
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _homeSound;
|
||||
SoundDescription _wallHitSound;
|
||||
SoundDescription _exitButtonSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
int _currentlyAnimating = -1;
|
||||
int _currentAnimFrame = -1;
|
||||
Common::Point _lastPosition = { -1, -1 };
|
||||
|
||||
uint32 _solveSoundPlayTime = 0;
|
||||
bool _solved = false;
|
||||
|
||||
uint32 _puzzleStartTime = 0;
|
||||
int _currentTimerGraphic = -1;
|
||||
|
||||
PuzzleType _puzzleType;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
322
engines/nancy/action/puzzle/cubepuzzle.cpp
Normal file
322
engines/nancy/action/puzzle/cubepuzzle.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/cubepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CubePuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[i], _pieceDests[i]);
|
||||
}
|
||||
|
||||
_placedPieces.resize(5, false);
|
||||
_curRotation = _startRotation;
|
||||
_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][0], _placedDest);
|
||||
}
|
||||
|
||||
void CubePuzzle::registerGraphics() {
|
||||
_curPiece.registerGraphics();
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void CubePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _cwCursorDest);
|
||||
readRect(stream, _ccwCursorDest);
|
||||
|
||||
readRect(stream, _placedDest);
|
||||
|
||||
// four pieces on the side, 1 on top
|
||||
_pieceSrcs.resize(5);
|
||||
_pieceDests.resize(5);
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
readRect(stream, _pieceSrcs[i]);
|
||||
readRect(stream, _pieceDests[i]);
|
||||
}
|
||||
|
||||
_placedSrcs.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
readRectArray(stream, _placedSrcs[i], 9);
|
||||
}
|
||||
|
||||
_startRotation = stream.readUint16LE();
|
||||
|
||||
_rotateSound.readNormal(stream);
|
||||
_pickUpSound.readNormal(stream);
|
||||
_placeDownSound.readNormal(stream);
|
||||
|
||||
_solveSceneIDs.resize(4);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_solveSceneIDs[i] = stream.readUint16LE();
|
||||
}
|
||||
_solveScene.readData(stream);
|
||||
_solveSceneIDs[3] = _solveScene._sceneChange.sceneID;
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void CubePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_placeDownSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
if (!_placedPieces[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_completed = true;
|
||||
|
||||
_state = kActionTrigger;
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
_solveScene._sceneChange.sceneID = _solveSceneIDs[_curRotation];
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_rotateSound);
|
||||
g_nancy->_sound->stopSound(_pickUpSound);
|
||||
g_nancy->_sound->stopSound(_placeDownSound);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CubePuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_completed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_pieceDests[i]).contains(input.mousePos)) {
|
||||
if (_pickedUpPiece == -1 && _placedPieces[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_pickedUpPiece != -1) {
|
||||
_curPiece.putDown();
|
||||
_curPiece.setVisible(false);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
|
||||
_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != (int)i && !_placedPieces[i]) {
|
||||
// Clicked on another piece while holding, swap them
|
||||
_drawSurface.fillRect(_pieceDests[i], _drawSurface.getTransparentColor());
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
|
||||
_pickedUpPiece = i;
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
|
||||
_curPiece.pickUp();
|
||||
_curPiece.setVisible(true);
|
||||
_curPiece._drawSurface.create(_image, _pieceSrcs[i]);
|
||||
_curPiece.setVisible(true);
|
||||
} else {
|
||||
// Clicked the dest of the picked up piece, or an already placed one; simply put it down
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
// Piece picked up
|
||||
_curPiece.handleInput(input);
|
||||
|
||||
bool canPlace = false;
|
||||
|
||||
if (_pickedUpPiece == 0) {
|
||||
// Top piece can ble placed only in the correct rotation (which is 0)
|
||||
canPlace = _curRotation == 0;
|
||||
} else {
|
||||
canPlace = _curRotation + 1 == _pickedUpPiece;
|
||||
}
|
||||
|
||||
if (canPlace) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_placedDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_placedPieces[_pickedUpPiece] = true;
|
||||
_curPiece.putDown();
|
||||
_curPiece.setVisible(false);
|
||||
_pickedUpPiece = -1;
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
rotateBase(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CubePuzzle::rotateBase(int dir) {
|
||||
_drawSurface.fillRect(_placedDest, _drawSurface.getTransparentColor());
|
||||
|
||||
_curRotation += dir;
|
||||
if (_curRotation < 0) {
|
||||
_curRotation = 3;
|
||||
} else if (_curRotation > 3) {
|
||||
_curRotation = 0;
|
||||
}
|
||||
|
||||
uint srcSelect = 0;
|
||||
|
||||
if (_placedPieces[0]) {
|
||||
if (_placedPieces[_curRotation + 1]) {
|
||||
// Front piece is in, other checks are unnecessary
|
||||
srcSelect = 2;
|
||||
} else {
|
||||
int leftIndex = _curRotation + 1;
|
||||
if (leftIndex > 3) {
|
||||
leftIndex = 0;
|
||||
}
|
||||
|
||||
int rightIndex = _curRotation - 1;
|
||||
if (rightIndex < 0) {
|
||||
rightIndex = 3;
|
||||
}
|
||||
|
||||
int backIndex = (_curRotation + 2) % 4;
|
||||
|
||||
if (_placedPieces[leftIndex + 1]) {
|
||||
// Left piece is in, check for right or back
|
||||
if (_placedPieces[rightIndex + 1]) {
|
||||
// Draw left & right piece
|
||||
srcSelect = 8;
|
||||
} else if (_placedPieces[backIndex]) {
|
||||
// Draw left & back piece
|
||||
srcSelect = 6;
|
||||
} else {
|
||||
// Draw left piece only
|
||||
srcSelect = 3;
|
||||
}
|
||||
} else if (_placedPieces[rightIndex + 1]) {
|
||||
// Right piece is in, check for back
|
||||
if (_placedPieces[backIndex + 1]) {
|
||||
// Draw right & back piece
|
||||
srcSelect = 7;
|
||||
} else {
|
||||
// Draw right piece only
|
||||
srcSelect = 4;
|
||||
}
|
||||
} else if (_placedPieces[backIndex + 1]) {
|
||||
// Draw back piece only
|
||||
srcSelect = 5;
|
||||
} else {
|
||||
// Draw top piece only
|
||||
srcSelect = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][srcSelect], _placedDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
101
engines/nancy/action/puzzle/cubepuzzle.h
Normal file
101
engines/nancy/action/puzzle/cubepuzzle.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_CUBEPUZZLE_H
|
||||
#define NANCY_ACTION_CUBEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Similar idea to AssemblyPuzzle; the player is provided with broken pieces of
|
||||
// a cube, and has to assemble it back together. However, the data is completely
|
||||
// different to AssemblyPuzzle's, so we need separate implementations.
|
||||
class CubePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
CubePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~CubePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "CubePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void rotateBase(int dir);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _cwCursorDest;
|
||||
Common::Rect _ccwCursorDest;
|
||||
|
||||
Common::Rect _placedDest;
|
||||
|
||||
Common::Array<Common::Rect> _pieceSrcs; // Used only when not placed
|
||||
Common::Array<Common::Rect> _pieceDests; // Used only when not placed
|
||||
|
||||
// 4 arrays with 9 rects each; every rect corresponds to a specific permutation of placed pieces:
|
||||
// - bottom piece only (start state)
|
||||
// - bottom & top piece
|
||||
// - front piece placed
|
||||
// - left piece placed
|
||||
// - back piece placed
|
||||
// - right piece placed
|
||||
// - left + back piece placed
|
||||
// - right + back piece placed
|
||||
// - left + right piece placed
|
||||
Common::Array<Common::Array<Common::Rect>> _placedSrcs;
|
||||
|
||||
uint16 _startRotation = 0;
|
||||
|
||||
SoundDescription _rotateSound;
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _placeDownSound;
|
||||
|
||||
// Multiple solve scenes, one for each cube orientation
|
||||
Common::Array<uint> _solveSceneIDs;
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Misc::MouseFollowObject _curPiece;
|
||||
|
||||
Common::Array<bool> _placedPieces;
|
||||
int _pickedUpPiece = -1;
|
||||
|
||||
int _curRotation = 0;
|
||||
bool _completed = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_CUBEPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/cuttingpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/cuttingpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/cuttingpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CuttingPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void CuttingPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 8 Art Studio Lathe Puzzle");
|
||||
NancySceneState.setEventFlag(341, g_nancy->_true); // Set dowel puzzle flag to solved
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 3854;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void CuttingPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void CuttingPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/cuttingpuzzle.h
Normal file
51
engines/nancy/action/puzzle/cuttingpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
#define NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Art Studio Lathe Puzzle in Nancy 8
|
||||
|
||||
class CuttingPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
CuttingPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~CuttingPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "CuttingPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
473
engines/nancy/action/puzzle/hamradiopuzzle.cpp
Normal file
473
engines/nancy/action/puzzle/hamradiopuzzle.cpp
Normal file
@@ -0,0 +1,473 @@
|
||||
/* 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 "common/random.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/hamradiopuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
static const char *morseCodeTable[] = {
|
||||
".-", "-...", "-.-.", "-..", // a, b, c, d
|
||||
".", "..-.", "--.", "....", // e, f, g, h
|
||||
"..", ".---", "-.-", ".-..", // i, j, k, l
|
||||
"--", "-.", "---", ".--.", // m, n, o, p
|
||||
"--.-", ".-.", "...", "-", // q, r, s, t
|
||||
"..-", "...-", ".--", "-..-", // u, v, w, x
|
||||
"-.--", "--.." // y, z
|
||||
};
|
||||
|
||||
void HamRadioPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::updateGraphics() {
|
||||
if (_digitsRolling) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
bool allDigitsCorrect = true;
|
||||
|
||||
for (uint i = 0; i < _numDigits; ++i) {
|
||||
if (curTime > _nextDigitFrameTimes[i]) {
|
||||
uint targetFrame = (_curDigits[i] == 0 ? (10 - 1) * 3 : (_curDigits[i] - 1) * 3);
|
||||
|
||||
if (_displayedDigitFrames[i] == targetFrame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++_displayedDigitFrames[i] >= 10 * 3) {
|
||||
_displayedDigitFrames[i] = 0;
|
||||
}
|
||||
|
||||
// Have we arrived at the correct digit?
|
||||
if (_displayedDigitFrames[i] != targetFrame) {
|
||||
// If not, set the next frame time depending on how far away the next digit is
|
||||
// This way, the animation slows down as we approach the correct digit
|
||||
int frameDifference = targetFrame - _displayedDigitFrames[i];
|
||||
if (frameDifference < 0) {
|
||||
frameDifference += 10 * 3;
|
||||
}
|
||||
|
||||
if (_nextDigitFrameTimes[i] == 0) {
|
||||
_nextDigitFrameTimes[i] = curTime;
|
||||
}
|
||||
|
||||
switch (frameDifference) {
|
||||
case 1:
|
||||
_nextDigitFrameTimes[i] += 300; break;
|
||||
case 2:
|
||||
// fall through
|
||||
case 3:
|
||||
_nextDigitFrameTimes[i] += 200; break;
|
||||
case 4:
|
||||
// fall through
|
||||
case 5:
|
||||
_nextDigitFrameTimes[i] += 100; break;
|
||||
default:
|
||||
_nextDigitFrameTimes[i] += 50; break;
|
||||
}
|
||||
|
||||
// Mark digits as incorrect
|
||||
allDigitsCorrect = false;
|
||||
}
|
||||
|
||||
// Play the rolling sound
|
||||
g_nancy->_sound->loadSound(_digitRollSound, nullptr, true);
|
||||
g_nancy->_sound->playSound(_digitRollSound);
|
||||
|
||||
// Finally, change the digit graphic
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[_displayedDigitFrames[i]], _digitDests[i]);
|
||||
_needsRedraw = true;
|
||||
} else {
|
||||
// Still animating a digit, so we can't be at the correct one yet
|
||||
allDigitsCorrect = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDigitsCorrect) {
|
||||
// We've arrived at the correct digits, end the animation state
|
||||
_digitsRolling = false;
|
||||
Common::fill(_nextDigitFrameTimes.begin(), _nextDigitFrameTimes.end(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::setFrequency(const Common::Array<uint16> &freq) {
|
||||
_isOnCorrectFrequency = false;
|
||||
_curMorseString.clear();
|
||||
_curCharString.clear();
|
||||
|
||||
if (freq == _startFreq.frequency) {
|
||||
// Check start frequency
|
||||
_startFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(_startFreq.flag);
|
||||
} else if (freq == _correctFreq.frequency) {
|
||||
// Check correct transmission frequency
|
||||
_correctFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(_correctFreq.flag);
|
||||
_isOnCorrectFrequency = true;
|
||||
} else {
|
||||
// Check other frequencies
|
||||
for (auto &otherFreq : _otherFrequencies) {
|
||||
if (freq == otherFreq.frequency) {
|
||||
otherFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(otherFreq.flag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No frequency found, pick random "bad" sound
|
||||
// This is re-rolled every time a bad frequency is connected to, even if the player
|
||||
// hasn't inputted any new digits
|
||||
_badFrequencySounds[g_nancy->_randomSource->getRandomNumber(_badFrequencySounds.size() - 1)].loadAndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::CCSound::readData(Common::SeekableReadStream &stream) {
|
||||
char buf[100];
|
||||
stream.read(buf, 100);
|
||||
assembleTextLine(buf, text, 100);
|
||||
sound.readNormal(stream);
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::CCSound::loadAndPlay() {
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
|
||||
if (text.size() && ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(text);
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::Frequency::readData(Common::SeekableReadStream &stream, uint16 numDigits) {
|
||||
frequency.resize(numDigits);
|
||||
for (uint i = 0; i < numDigits; ++i) {
|
||||
frequency[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - numDigits) * 2);
|
||||
|
||||
sound.readData(stream);
|
||||
flag.label = stream.readUint16LE();
|
||||
flag.flag = stream.readByte();
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numDigits = stream.readUint16LE();
|
||||
|
||||
_startFreq.readData(stream, _numDigits);
|
||||
_correctFreq.readData(stream, _numDigits);
|
||||
|
||||
_passwordMaxSize = stream.readUint16LE();
|
||||
readFilename(stream, _password); // not a filename
|
||||
_passwordFlag.label = stream.readUint16LE();
|
||||
_passwordFlag.flag = stream.readByte();
|
||||
readFilename(stream, _codeWord); // not a filename
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
readRectArray(stream, _digitDests, _numDigits, 8);
|
||||
readRectArray(stream, _buttonDests, 10 + 6);
|
||||
|
||||
readRectArray(stream, _digitSrcs, 10 * 3); // digits 0-9, plus 2 inbetweens per digit
|
||||
readRectArray(stream, _buttonSrcs, 10 + 6);
|
||||
|
||||
_digitRollSound.readNormal(stream);
|
||||
_frequencyButtonSound.readData(stream);
|
||||
_connectButtonSound.readData(stream);
|
||||
_dotButtonSound.readData(stream);
|
||||
_dashButtonSound.readData(stream);
|
||||
_sendButtonSound.readData(stream);
|
||||
_deleteButtonSound.readData(stream);
|
||||
_resetButtonSound.readData(stream);
|
||||
_badLetterSound.readData(stream);
|
||||
_longMorseOtherSound.readData(stream);
|
||||
_goodPasswordSound.readData(stream);
|
||||
_longMorseSound.readData(stream);
|
||||
|
||||
_badFrequencySounds.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_badFrequencySounds[i].readData(stream);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readData(stream);
|
||||
|
||||
readRect(stream, _exitButtonDest);
|
||||
readRect(stream, _exitButtonSrc);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
_exitSoundDelay = stream.readUint16LE();
|
||||
_exitSound.readNormal(stream);
|
||||
|
||||
uint16 numOtherFreqs = stream.readUint16LE();
|
||||
_otherFrequencies.resize(numOtherFreqs);
|
||||
for (uint i = 0; i < numOtherFreqs; ++i) {
|
||||
_otherFrequencies[i].readData(stream, _numDigits);
|
||||
}
|
||||
|
||||
_curDigits.resize(_numDigits, 0);
|
||||
_displayedDigitFrames.resize(_numDigits, 0);
|
||||
_nextDigitFrameTimes.resize(_numDigits, 0);
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_digitRollSound);
|
||||
setFrequency(_startFreq.frequency);
|
||||
_curDigits = _startFreq.frequency;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_pressedButton != kNone && g_nancy->getTotalPlayTime() > _buttonEndTime) {
|
||||
bool isDot = false;
|
||||
|
||||
// Check for button presses
|
||||
switch (_pressedButton) {
|
||||
case kConnect:
|
||||
setFrequency(_curDigits);
|
||||
break;
|
||||
case kDot:
|
||||
isDot = true;
|
||||
// fall through
|
||||
case kDash:
|
||||
_curMorseString += isDot ? '.' : '-'; // Original engine uses the captions inside the dot and dash sounds
|
||||
|
||||
if (_curMorseString.size() > 4) {
|
||||
_curMorseString.clear();
|
||||
_badLetterSound.loadAndPlay();
|
||||
} else {
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_curMorseString);
|
||||
NancySceneState.getTextbox().setOverrideFont(3); // Original engine pushes <f3> tag instead
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kSend: {
|
||||
bool foundCorrect = false;
|
||||
|
||||
if (_curMorseString.size()) {
|
||||
for (uint i = 0; i < ARRAYSIZE(morseCodeTable); ++i) {
|
||||
if (_curMorseString == morseCodeTable[i]) {
|
||||
foundCorrect = true;
|
||||
_curCharString += ('a' + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if above maximum length string
|
||||
if (_curMorseString.size() > 10) {
|
||||
_longMorseSound.loadAndPlay();
|
||||
_curCharString.clear();
|
||||
_curMorseString.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Morse code is incorrect
|
||||
if (!foundCorrect) {
|
||||
_badLetterSound.loadAndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kDelete:
|
||||
_curMorseString.clear();
|
||||
|
||||
if (_curCharString.size() > 10) {
|
||||
// Password is above max size, clear
|
||||
_curCharString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
_longMorseSound.loadAndPlay();
|
||||
} else if (_solvedPassword && _curCharString.size() > _passwordMaxSize) {
|
||||
// Password is above max size, clear
|
||||
_curCharString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
_longMorseOtherSound.loadAndPlay();
|
||||
}
|
||||
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_curCharString);
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
|
||||
if (_isOnCorrectFrequency) {
|
||||
// When transmitting on right frequency, check password/code word
|
||||
if (!_solvedPassword) {
|
||||
// Password not solved, check against it
|
||||
if (_curCharString == _password) {
|
||||
_solvedPassword = true;
|
||||
NancySceneState.setEventFlag(_passwordFlag);
|
||||
_curCharString.clear();
|
||||
_goodPasswordSound.loadAndPlay();
|
||||
}
|
||||
} else {
|
||||
// Password solved, check against codeword
|
||||
if (_curCharString == _codeWord) {
|
||||
_solvedCodeword = true;
|
||||
_curCharString.clear();
|
||||
|
||||
_solveSound.loadAndPlay(); // Sound delay is ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kReset:
|
||||
_curCharString.clear();
|
||||
_curMorseString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
|
||||
break;
|
||||
default:
|
||||
// Number digit
|
||||
for (int i = 0; i < _numDigits - 1; ++i) {
|
||||
_curDigits[i] = _curDigits[i + 1];
|
||||
}
|
||||
|
||||
_curDigits.back() = (_pressedButton == 9 ? 0 : _pressedButton + 1);
|
||||
_digitsRolling = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_drawSurface.fillRect(_buttonDests[_pressedButton], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
_pressedButton = kNone;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_digitsRolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solvedCodeword) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
// Fail sound is ignored
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::handleInput(NancyInput &input) {
|
||||
if (_digitsRolling || _state != kRun || _pressedButton != kNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle exit button
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
for (uint i = 0; i < _curDigits.size(); ++i) {
|
||||
_curDigits[i] = 0;
|
||||
}
|
||||
_digitsRolling = true;
|
||||
_drawSurface.blitFrom(_image, _exitButtonSrc, _exitButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle other buttons
|
||||
for (uint i = 0; i < _buttonDests.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[i]).contains(input.mousePos)) {
|
||||
if (i >= 10 || _pressedButton == kNone) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_pressedButton = i;
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
|
||||
CCSound *soundToPlay = nullptr;
|
||||
|
||||
switch (i) {
|
||||
case kConnect:
|
||||
soundToPlay = &_connectButtonSound; break;
|
||||
case kDot:
|
||||
soundToPlay = &_dotButtonSound; break;
|
||||
case kDash:
|
||||
soundToPlay = &_dashButtonSound; break;
|
||||
case kSend:
|
||||
soundToPlay = &_sendButtonSound; break;
|
||||
case kDelete:
|
||||
soundToPlay = &_deleteButtonSound; break;
|
||||
case kReset:
|
||||
soundToPlay = &_resetButtonSound; break;
|
||||
default:
|
||||
soundToPlay = &_frequencyButtonSound; break;
|
||||
}
|
||||
|
||||
// Do NOT use the loadAndPlaySound() function, since the dot/dash sounds have ./- captions
|
||||
g_nancy->_sound->loadSound(soundToPlay->sound, nullptr, true);
|
||||
g_nancy->_sound->playSound(soundToPlay->sound);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pressedButton != kNone) {
|
||||
_buttonEndTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
137
engines/nancy/action/puzzle/hamradiopuzzle.h
Normal file
137
engines/nancy/action/puzzle/hamradiopuzzle.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
#define NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle that has the player input radio frequencies, and
|
||||
// send morse code data via ham radio. Used in nancy6.
|
||||
class HamRadioPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
HamRadioPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~HamRadioPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "HamRadioPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void setFrequency(const Common::Array<uint16> &freq);
|
||||
|
||||
// 0-10 are the digit buttons
|
||||
enum ButtonPress { kNone = -1, kConnect = 10, kDot = 11, kDash = 12, kSend = 13, kDelete = 14, kReset = 15 };
|
||||
|
||||
struct CCSound {
|
||||
Common::String text;
|
||||
SoundDescription sound;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream);
|
||||
void loadAndPlay();
|
||||
};
|
||||
|
||||
struct Frequency {
|
||||
Common::Array<uint16> frequency;
|
||||
CCSound sound;
|
||||
FlagDescription flag;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream, uint16 numDigits);
|
||||
};
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numDigits = 0;
|
||||
|
||||
Frequency _startFreq;
|
||||
Frequency _correctFreq;
|
||||
|
||||
uint16 _passwordMaxSize = 0;
|
||||
Common::String _password;
|
||||
FlagDescription _passwordFlag;
|
||||
Common::String _codeWord;
|
||||
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
|
||||
Common::Array<Common::Rect> _digitSrcs;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
|
||||
SoundDescription _digitRollSound;
|
||||
CCSound _frequencyButtonSound;
|
||||
CCSound _connectButtonSound;
|
||||
CCSound _dotButtonSound;
|
||||
CCSound _dashButtonSound;
|
||||
CCSound _sendButtonSound;
|
||||
CCSound _deleteButtonSound;
|
||||
CCSound _resetButtonSound;
|
||||
CCSound _badLetterSound;
|
||||
CCSound _longMorseOtherSound;
|
||||
CCSound _goodPasswordSound;
|
||||
CCSound _longMorseSound;
|
||||
Common::Array<CCSound> _badFrequencySounds;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0; // not used
|
||||
CCSound _solveSound;
|
||||
|
||||
Common::Rect _exitButtonDest;
|
||||
Common::Rect _exitButtonSrc;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
uint16 _exitSoundDelay = 0; // not used
|
||||
SoundDescription _exitSound; // not used
|
||||
|
||||
Common::Array<Frequency> _otherFrequencies;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
// Frequency display data
|
||||
bool _digitsRolling = true;
|
||||
Common::Array<uint16> _curDigits;
|
||||
Common::Array<uint16> _displayedDigitFrames;
|
||||
Common::Array<uint32> _nextDigitFrameTimes;
|
||||
|
||||
// Sent morse code
|
||||
Common::String _curMorseString;
|
||||
Common::String _curCharString;
|
||||
|
||||
int _pressedButton = kNone;
|
||||
uint32 _buttonEndTime = 0;
|
||||
|
||||
bool _isOnCorrectFrequency = false;
|
||||
bool _solvedPassword = false;
|
||||
bool _solvedCodeword = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
231
engines/nancy/action/puzzle/leverpuzzle.cpp
Normal file
231
engines/nancy/action/puzzle/leverpuzzle.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/* 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/resource.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/leverpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void LeverPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void LeverPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_srcRects.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_srcRects.push_back(Common::Array<Common::Rect>());
|
||||
_srcRects.back().reserve(3);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_srcRects.back().push_back(Common::Rect());
|
||||
readRect(stream, _srcRects.back().back());
|
||||
}
|
||||
}
|
||||
|
||||
_destRects.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_destRects.push_back(Common::Rect());
|
||||
readRect(stream, _destRects.back());
|
||||
|
||||
if (leverID == 0) {
|
||||
_screenPosition = _destRects.back();
|
||||
} else {
|
||||
_screenPosition.extend(_destRects.back());
|
||||
}
|
||||
}
|
||||
|
||||
_playerSequence.reserve(3);
|
||||
_leverDirection.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_playerSequence.push_back(stream.readByte());
|
||||
_leverDirection.push_back(true);
|
||||
}
|
||||
|
||||
_correctSequence.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_correctSequence.push_back(stream.readByte());
|
||||
}
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
_noMoveSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void LeverPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_noMoveSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
drawLever(i);
|
||||
}
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
if (_playerSequence[i] != _correctSequence[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
break;
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
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:
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_noMoveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void LeverPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
bool isMoving = false;
|
||||
// Hardcoded by the original engine
|
||||
switch (i) {
|
||||
case 0:
|
||||
isMoving = true;
|
||||
break;
|
||||
case 1:
|
||||
if (_playerSequence[0] == 1) {
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case 2:
|
||||
if (_playerSequence[0] == 2) {
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
|
||||
if (_leverDirection[i]) {
|
||||
// Moving down
|
||||
if (_playerSequence[i] == 3) {
|
||||
--_playerSequence[i];
|
||||
_leverDirection[i] = false;
|
||||
} else {
|
||||
++_playerSequence[i];
|
||||
}
|
||||
} else {
|
||||
// Moving up
|
||||
if (_playerSequence[i] == 0) {
|
||||
++_playerSequence[i];
|
||||
_leverDirection[i] = true;
|
||||
} else {
|
||||
--_playerSequence[i];
|
||||
}
|
||||
}
|
||||
|
||||
drawLever(i);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_noMoveSound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LeverPuzzle::drawLever(uint id) {
|
||||
Common::Point destPoint(_destRects[id].left - _screenPosition.left, _destRects[id].top - _screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _srcRects[id][_playerSequence[id]], destPoint);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
70
engines/nancy/action/puzzle/leverpuzzle.h
Normal file
70
engines/nancy/action/puzzle/leverpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_LEVERPUZZLE_H
|
||||
#define NANCY_ACTION_LEVERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class LeverPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
LeverPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~LeverPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Array<Common::Rect>> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<byte> _correctSequence;
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _noMoveSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Common::Array<byte> _playerSequence;
|
||||
Common::Array<bool> _leverDirection;
|
||||
Graphics::ManagedSurface _image;
|
||||
Time _solveSoundPlayTime;
|
||||
SolveState _solveState = kNotSolved;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "LeverPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawLever(uint id);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_LEVERPUZZLE_H
|
||||
96
engines/nancy/action/puzzle/matchpuzzle.cpp
Normal file
96
engines/nancy/action/puzzle/matchpuzzle.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/matchpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MatchPuzzle::init() {
|
||||
// TODO
|
||||
//_screenPosition = _displayBounds;
|
||||
|
||||
//_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
//_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_overlayName, _image);
|
||||
RenderActionRecord::init();
|
||||
}
|
||||
|
||||
void MatchPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the main menu
|
||||
warning("STUB - Nancy 8 flag game");
|
||||
_exitSceneChange.execute();
|
||||
}
|
||||
|
||||
void MatchPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _overlayName);
|
||||
readFilename(stream, _flagPointBackgroundName);
|
||||
|
||||
stream.skip(2); // TODO (value: 5)
|
||||
stream.skip(2); // TODO (value: 7)
|
||||
stream.skip(2); // 26 flags
|
||||
|
||||
readRect(stream,_shuffleButtonRect);
|
||||
readRectArray(stream, _flagRects, 26);
|
||||
|
||||
stream.skip(103); // TODO (mostly zeroes)
|
||||
|
||||
readFilenameArray(stream, _flagNames, 26);
|
||||
|
||||
stream.skip(132); // TODO (zeroes)
|
||||
stream.skip(173); // TODO
|
||||
|
||||
_slotWinSound.readNormal(stream);
|
||||
_shuffleSound.readNormal(stream);
|
||||
_cardPlaceSound.readNormal(stream);
|
||||
|
||||
_solveSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_matchSuccessSound.readNormal(stream);
|
||||
_exitSceneChange.readData(stream);
|
||||
|
||||
stream.skip(16); // TODO
|
||||
}
|
||||
|
||||
void MatchPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
70
engines/nancy/action/puzzle/matchpuzzle.h
Normal file
70
engines/nancy/action/puzzle/matchpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MATCHPUZZLE_H
|
||||
#define NANCY_ACTION_MATCHPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Flag puzzle in Nancy 8
|
||||
|
||||
class MatchPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MatchPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MatchPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MatchPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
//Common::Rect _displayBounds;
|
||||
|
||||
Common::Rect _shuffleButtonRect;
|
||||
Common::Array<Common::Rect> _flagRects;
|
||||
|
||||
Common::Path _overlayName;
|
||||
Common::Path _flagPointBackgroundName;
|
||||
|
||||
Common::StringArray _flagNames;
|
||||
|
||||
SoundDescription _slotWinSound;
|
||||
SoundDescription _shuffleSound;
|
||||
SoundDescription _cardPlaceSound;
|
||||
SoundDescription _matchSuccessSound;
|
||||
|
||||
SceneChangeWithFlag _solveSceneChange;
|
||||
SceneChangeWithFlag _exitSceneChange;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MATCHPUZZLE_H
|
||||
572
engines/nancy/action/puzzle/mazechasepuzzle.cpp
Normal file
572
engines/nancy/action/puzzle/mazechasepuzzle.cpp
Normal file
@@ -0,0 +1,572 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MazeChasePuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _startLocations.size(); ++i) {
|
||||
_pieces.push_back(Piece(_z + i + 1));
|
||||
_pieces[i]._drawSurface.create(_image, i == 0 ? _playerSrc : _enemySrc);
|
||||
Common::Rect pos = getScreenPosition(_startLocations[i]);
|
||||
_pieces[i].moveTo(pos);
|
||||
_pieces[i]._gridPos = _startLocations[i];
|
||||
_pieces[i]._lastPos = _pieces[i]._gridPos;
|
||||
_pieces[i].setVisible(true);
|
||||
_pieces[i].setTransparent(true);
|
||||
}
|
||||
|
||||
if (NancySceneState.getEventFlag(_solveScene._flag)) {
|
||||
_drawSurface.blitFrom(_image, _lightSrc, _lightDest);
|
||||
}
|
||||
|
||||
drawGrid();
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::updateGraphics() {
|
||||
if (_currentAnimFrame != -1) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_moveSound) || g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Framerate-dependent animation. Should be fine since we limit the engine to ~60fps
|
||||
++_currentAnimFrame;
|
||||
|
||||
if (_reset) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &cur = _pieces[i];
|
||||
if (cur._gridPos != cur._lastPos) {
|
||||
bool horizontal = cur._gridPos.x != cur._lastPos.x;
|
||||
|
||||
Common::Rect destRect = getScreenPosition(cur._lastPos);
|
||||
Common::Rect endPos = getScreenPosition(cur._gridPos);
|
||||
|
||||
// Make sure to adjust the frame id for enemies
|
||||
int frame = (i == 0) ? _currentAnimFrame : _currentAnimFrame - 1;
|
||||
|
||||
Common::Point dest(destRect.left, destRect.top);
|
||||
if (horizontal) {
|
||||
dest.x = destRect.left + (endPos.left - dest.x) * frame / _framesPerMove;
|
||||
} else {
|
||||
dest.y = destRect.top + (endPos.top - dest.y) * frame / _framesPerMove;
|
||||
}
|
||||
|
||||
cur.moveTo(dest);
|
||||
|
||||
if (frame == _framesPerMove) {
|
||||
cur._lastPos = cur._gridPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentAnimFrame > 0) {
|
||||
if (!_solved) {
|
||||
// Make sure not to move pieces when the player is about to lose
|
||||
bool playerRanIntoEnemy = false;
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (_pieces[0]._gridPos == _pieces[i]._gridPos) {
|
||||
playerRanIntoEnemy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playerRanIntoEnemy) {
|
||||
// Each enemy moves one frame after the last one
|
||||
enemyMovement(_currentAnimFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentAnimFrame == 1) {
|
||||
// Clear the buttons
|
||||
Common::Rect fill = _upButtonDest;
|
||||
fill.extend(_downButtonDest);
|
||||
fill.extend(_leftButtonDest);
|
||||
fill.extend(_rightButtonDest);
|
||||
fill.extend(_resetButtonDest);
|
||||
_drawSurface.fillRect(fill, _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
} else if (_currentAnimFrame >= _framesPerMove + 1) {
|
||||
_currentAnimFrame = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint width = stream.readUint16LE();
|
||||
uint height = stream.readUint16LE();
|
||||
uint numEnemies = stream.readUint16LE();
|
||||
|
||||
_exitPos.x = stream.readUint16LE();
|
||||
_exitPos.y = stream.readUint16LE();
|
||||
|
||||
_grid.resize(height, Common::Array<uint16>(width));
|
||||
for (uint y = 0; y < height; ++y) {
|
||||
for (uint x = 0; x < width; ++x) {
|
||||
_grid[y][x] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - width) * 2);
|
||||
}
|
||||
stream.skip((8 - height) * 8 * 2);
|
||||
|
||||
_startLocations.resize(numEnemies + 1);
|
||||
for (uint i = 0; i < _startLocations.size(); ++i) {
|
||||
_startLocations[i].x = stream.readUint16LE();
|
||||
_startLocations[i].y = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _playerSrc);
|
||||
readRect(stream, _enemySrc);
|
||||
readRect(stream, _verticalWallSrc);
|
||||
readRect(stream, _horizontalWallSrc);
|
||||
readRect(stream, _lightSrc);
|
||||
|
||||
readRect(stream, _upButtonSrc);
|
||||
readRect(stream, _rightButtonSrc);
|
||||
readRect(stream, _downButtonSrc);
|
||||
readRect(stream, _leftButtonSrc);
|
||||
readRect(stream, _resetButtonSrc);
|
||||
|
||||
_gridPos.x = stream.readUint32LE();
|
||||
_gridPos.y = stream.readUint32LE();
|
||||
|
||||
readRect(stream, _lightDest);
|
||||
|
||||
readRect(stream, _upButtonDest);
|
||||
readRect(stream, _rightButtonDest);
|
||||
readRect(stream, _downButtonDest);
|
||||
readRect(stream, _leftButtonDest);
|
||||
readRect(stream, _resetButtonDest);
|
||||
|
||||
_lineWidth = stream.readUint16LE();
|
||||
_framesPerMove = stream.readUint16LE();
|
||||
|
||||
_failSound.readNormal(stream);
|
||||
_moveSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_currentAnimFrame != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pieces[0]._gridPos == _exitPos) {
|
||||
_pieces[0]._gridPos = _exitPos + Common::Point(_exitPos.x == 0 ? -1 : 1, 0);
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
} else {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i]._gridPos == _pieces[0]._gridPos) {
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
++_currentAnimFrame;
|
||||
_reset = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solveSoundPlayTime == 0) {
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
return;
|
||||
} else if (_solveSoundPlayTime < g_nancy->getTotalPlayTime()) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _solved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentAnimFrame != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect buttonHotspot = _upButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallUp)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_pieces[0]._gridPos.y;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _upButtonSrc, _upButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _rightButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallRight)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_pieces[0]._gridPos.x;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _rightButtonSrc, _rightButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _downButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallDown)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_pieces[0]._gridPos.y;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _downButtonSrc, _downButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _leftButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallLeft)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_pieces[0]._gridPos.x;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _leftButtonSrc, _leftButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _resetButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _resetButtonSrc, _resetButtonDest);
|
||||
_needsRedraw = true;
|
||||
_reset = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect MazeChasePuzzle::getScreenPosition(Common::Point gridPos) {
|
||||
Common::Rect dest = _playerSrc;
|
||||
|
||||
dest.moveTo(0, 0);
|
||||
|
||||
dest.right -= 1;
|
||||
dest.bottom -= 1;
|
||||
|
||||
dest.moveTo(_gridPos);
|
||||
dest.translate(gridPos.x * _lineWidth, gridPos.y * _lineWidth);
|
||||
dest.translate(gridPos.x * dest.width(), gridPos.y *dest.height());
|
||||
|
||||
if (gridPos.x < 0 || gridPos.x >= (int)_grid[0].size()) {
|
||||
// Make sure the end position is in the middle of the dancers
|
||||
dest.translate(12, 0);
|
||||
}
|
||||
|
||||
dest.right += 1;
|
||||
dest.bottom += 1;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::drawGrid() {
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[y].size(); ++x) {
|
||||
uint16 cell = _grid[y][x];
|
||||
Common::Rect cellRect = getScreenPosition(Common::Point(x, y));
|
||||
Common::Point dest(cellRect.left, cellRect.top);
|
||||
|
||||
if (cell == kWallUp || cell == kWallUpDown) {
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest - Common::Point(0, _lineWidth));
|
||||
}
|
||||
|
||||
if (cell == kWallDown || cell == kWallUpDown) {
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest + Common::Point(0, cellRect.height() - 1));
|
||||
}
|
||||
|
||||
if (cell == kWallLeft || cell == kWallLeftRight) {
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest - Common::Point(_lineWidth, 0));
|
||||
}
|
||||
|
||||
if (cell == kWallRight || cell == kWallLeftRight) {
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest + Common::Point(cellRect.width() - 1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::enemyMovement(uint enemyID) {
|
||||
if (enemyID >= _pieces.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Piece &player = _pieces[0];
|
||||
Piece &enemy = _pieces[enemyID];
|
||||
Common::Point diff = player._gridPos - enemy._gridPos;
|
||||
|
||||
// First, try to move vertically
|
||||
if (diff.y) {
|
||||
if (diff.y > 0) {
|
||||
// Player is lower than enemy, try to move down
|
||||
if (canMove(enemyID, kWallDown)) {
|
||||
++enemy._gridPos.y;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Player is higher than enemy, try to move up
|
||||
if (canMove(enemyID, kWallUp)) {
|
||||
--enemy._gridPos.y;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then, try to move horizontally. Note that when the player is on the same row,
|
||||
// the enemy will not move if adjacent to a wall; this is intentional
|
||||
if (diff.x) {
|
||||
if (diff.x > 0) {
|
||||
// Player is to the enemy's right
|
||||
if (canMove(enemyID, kWallRight)) {
|
||||
++enemy._gridPos.x;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Player is to the enemy's left
|
||||
if (canMove(enemyID, kWallLeft)) {
|
||||
--enemy._gridPos.x;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MazeChasePuzzle::canMove(uint pieceID, WallType direction) {
|
||||
Piece &piece = _pieces[pieceID];
|
||||
switch (direction) {
|
||||
case kWallLeft :
|
||||
if ( piece._gridPos.x == 0 ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x - 1] == kWallRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x - 1] == kWallLeftRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeft ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeftRight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(-1, 0) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallRight :
|
||||
if ( piece._gridPos.x == (int)_grid[0].size() - 1 ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x + 1] == kWallLeft ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x + 1] == kWallLeftRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeftRight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(1, 0) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallUp :
|
||||
if ( piece._gridPos.y == 0 ||
|
||||
_grid[piece._gridPos.y - 1][piece._gridPos.x] == kWallDown ||
|
||||
_grid[piece._gridPos.y - 1][piece._gridPos.x] == kWallUpDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUp ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUpDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(0, -1) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallDown :
|
||||
if ( piece._gridPos.y == (int)_grid.size() - 1 ||
|
||||
_grid[piece._gridPos.y + 1][piece._gridPos.x] == kWallUp ||
|
||||
_grid[piece._gridPos.y + 1][piece._gridPos.x] == kWallUpDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUpDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(0, 1) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
default :
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::reset() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i]._gridPos = _pieces[i]._lastPos = _startLocations[i];
|
||||
_pieces[i].moveTo(getScreenPosition(_pieces[i]._gridPos));
|
||||
}
|
||||
|
||||
Common::Rect fill = _upButtonDest;
|
||||
fill.extend(_downButtonDest);
|
||||
fill.extend(_leftButtonDest);
|
||||
fill.extend(_rightButtonDest);
|
||||
fill.extend(_resetButtonDest);
|
||||
_drawSurface.fillRect(fill, _drawSurface.getTransparentColor());
|
||||
|
||||
_currentAnimFrame = -1;
|
||||
_reset = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
127
engines/nancy/action/puzzle/mazechasepuzzle.h
Normal file
127
engines/nancy/action/puzzle/mazechasepuzzle.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
#define NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Implements a puzzle introduced in nancy5 where the player controls
|
||||
// one piece being chased by several other pieces on a grid. Movement
|
||||
// is performed via buttons, and both player and enemy navigate one
|
||||
// tile at a time. Has some similarities to CollisionPuzzle, but was
|
||||
// different enough to warrant its own class.
|
||||
class MazeChasePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MazeChasePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MazeChasePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum WallType { kWallLeft = 1, kWallUp = 2, kWallRight = 3, kWallDown = 4, kWallLeftRight = 6, kWallUpDown = 6 };
|
||||
|
||||
class Piece : public RenderObject {
|
||||
public:
|
||||
Piece(uint z) : RenderObject(z) {}
|
||||
virtual ~Piece() {}
|
||||
|
||||
Common::Point _gridPos;
|
||||
Common::Point _lastPos;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return "MazeChasePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Rect getScreenPosition(Common::Point gridPos);
|
||||
void drawGrid();
|
||||
void enemyMovement(uint enemyID);
|
||||
bool canMove(uint pieceID, WallType direction);
|
||||
void reset();
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Point _exitPos = Common::Point(-1, -1);
|
||||
|
||||
Common::Array<Common::Array<uint16>> _grid;
|
||||
Common::Array<Common::Point> _startLocations;
|
||||
|
||||
Common::Rect _playerSrc;
|
||||
Common::Rect _enemySrc;
|
||||
Common::Rect _verticalWallSrc;
|
||||
Common::Rect _horizontalWallSrc;
|
||||
Common::Rect _lightSrc;
|
||||
|
||||
Common::Rect _upButtonSrc;
|
||||
Common::Rect _rightButtonSrc;
|
||||
Common::Rect _downButtonSrc;
|
||||
Common::Rect _leftButtonSrc;
|
||||
Common::Rect _resetButtonSrc;
|
||||
|
||||
Common::Point _gridPos;
|
||||
|
||||
Common::Rect _lightDest;
|
||||
|
||||
Common::Rect _upButtonDest;
|
||||
Common::Rect _rightButtonDest;
|
||||
Common::Rect _downButtonDest;
|
||||
Common::Rect _leftButtonDest;
|
||||
Common::Rect _resetButtonDest;
|
||||
|
||||
uint16 _lineWidth = 0;
|
||||
uint16 _framesPerMove = 0;
|
||||
|
||||
SoundDescription _failSound;
|
||||
SoundDescription _moveSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
int _currentAnimFrame = -1;
|
||||
|
||||
uint32 _solveSoundPlayTime = 0;
|
||||
bool _solved = false;
|
||||
bool _reset = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/memorypuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/memorypuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/memorypuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MemoryPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void MemoryPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Memory puzzle");
|
||||
NancySceneState.setEventFlag(423, g_nancy->_true); // EV_Solved_Necklace_Box
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 3846;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void MemoryPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void MemoryPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/memorypuzzle.h
Normal file
51
engines/nancy/action/puzzle/memorypuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MEMORYPUZZLE_H
|
||||
#define NANCY_ACTION_MEMORYPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Memory puzzle (toy box) in Nancy 9
|
||||
|
||||
class MemoryPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MemoryPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MemoryPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MemoryPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MEMORYPUZZLE_H
|
||||
120
engines/nancy/action/puzzle/mouselightpuzzle.cpp
Normal file
120
engines/nancy/action/puzzle/mouselightpuzzle.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/mouselightpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MouseLightPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
|
||||
Graphics::ManagedSurface baseImage;
|
||||
g_nancy->_resource->loadImage(_imageName, baseImage);
|
||||
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getTransparentPixelFormat());
|
||||
_drawSurface.blitFrom(baseImage);
|
||||
((Graphics::Surface)_drawSurface).setAlpha(0);
|
||||
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
_maskCircle.create(_radius * 2, _radius * 2, g_nancy->_graphics->getInputPixelFormat());
|
||||
_maskCircle.clear();
|
||||
|
||||
if (_smoothEdges) {
|
||||
for (int y = -_radius; y < _radius; ++y) {
|
||||
for (int x = -_radius; x < _radius; ++x) {
|
||||
_maskCircle.setPixel(x + _radius, y + _radius, (uint16)(expf(-(float)((y * y + x * x) * (y * y + x * x)) / (float)(_radius * _radius * _radius * _radius / 4)) * 0xFF));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int y = -_radius; y < _radius; ++y) {
|
||||
for (int x = -_radius; x < _radius; ++x) {
|
||||
if (sqrt(y * y + x * x) < _radius) {
|
||||
_maskCircle.setPixel(x + _radius, y + _radius, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
_radius = stream.readByte();
|
||||
_smoothEdges = stream.readByte();
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastMousePos == input.mousePos) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastMousePos = input.mousePos;
|
||||
((Graphics::Surface)_drawSurface).setAlpha(0);
|
||||
_needsRedraw = true;
|
||||
|
||||
Common::Rect vpScreenPos = NancySceneState.getViewport().convertViewportToScreen(_screenPosition);
|
||||
if (!vpScreenPos.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point blitDestPoint = input.mousePos;
|
||||
blitDestPoint -= { vpScreenPos.left, vpScreenPos.top };
|
||||
blitDestPoint -= { _radius, _radius };
|
||||
|
||||
Common::Rect srcRect = _maskCircle.getBounds();
|
||||
Common::Rect::getBlitRect(blitDestPoint, srcRect, _drawSurface.getBounds());
|
||||
|
||||
// Copy over the transparency to the draw surface
|
||||
for (int y = srcRect.top; y < srcRect.bottom; ++y) {
|
||||
uint32 *drawSurfPtr = (uint32 *)_drawSurface.getBasePtr(blitDestPoint.x, y + blitDestPoint.y - srcRect.top);
|
||||
uint16 *circlePtr = (uint16 *)_maskCircle.getBasePtr(srcRect.left, y);
|
||||
for (int x = srcRect.left; x < srcRect.right; ++x) {
|
||||
*drawSurfPtr = (*drawSurfPtr & 0xFFFFFF00) | (byte)*circlePtr;
|
||||
++drawSurfPtr;
|
||||
++circlePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
63
engines/nancy/action/puzzle/mouselightpuzzle.h
Normal file
63
engines/nancy/action/puzzle/mouselightpuzzle.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MOUSELIGHTPUZZLE_H
|
||||
#define NANCY_ACTION_MOUSELIGHTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Shows a single image over the entire frame, with most of it blackened;
|
||||
// a circle around that follows the cursor reveals parts of the image.
|
||||
// Circle can have smooth or hard edges. Not actually a puzzle.
|
||||
|
||||
// TODO: Optimize blitting; currently, the whole screen is redrawn
|
||||
// TODO: Add noise to the circle mask; there are artifacts at low brightness
|
||||
class MouseLightPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MouseLightPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MouseLightPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MouseLightPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
byte _radius = 0;
|
||||
bool _smoothEdges = false;
|
||||
|
||||
Graphics::ManagedSurface _maskCircle;
|
||||
|
||||
Common::Point _lastMousePos;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
87
engines/nancy/action/puzzle/multibuildpuzzle.cpp
Normal file
87
engines/nancy/action/puzzle/multibuildpuzzle.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/multibuildpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MultiBuildPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
|
||||
SceneChangeDescription scene;
|
||||
uint16 sceneID = NancySceneState.getSceneInfo().sceneID;
|
||||
|
||||
switch (sceneID) {
|
||||
case 2025:
|
||||
warning("STUB - Nancy 9 Sand castle puzzle");
|
||||
scene.sceneID = 2024;
|
||||
break;
|
||||
case 2575:
|
||||
warning("STUB - Nancy 9 Sandwich making puzzle");
|
||||
NancySceneState.setEventFlag(428, g_nancy->_true); // EV_Solved_Sandwich_Bad
|
||||
NancySceneState.setEventFlag(429, g_nancy->_true); // EV_Solved_Sandwich_Good
|
||||
scene.sceneID = 2572;
|
||||
break;
|
||||
case 2585:
|
||||
warning("STUB - Nancy 9 Book sorting puzzle");
|
||||
NancySceneState.setEventFlag(397, g_nancy->_true); // Set puzzle flag to solved
|
||||
scene.sceneID = 2583;
|
||||
break;
|
||||
default:
|
||||
warning("MultiBuildPuzzle: Unknown scene %d", sceneID);
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/multibuildpuzzle.h
Normal file
51
engines/nancy/action/puzzle/multibuildpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MULTIBUILDPUZZLE_H
|
||||
#define NANCY_ACTION_MULTIBUILDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Puzzle in Nancy 9, where an item is built from smaller pieces
|
||||
|
||||
class MultiBuildPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MultiBuildPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MultiBuildPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MultiBuildPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
79
engines/nancy/action/puzzle/onebuildpuzzle.cpp
Normal file
79
engines/nancy/action/puzzle/onebuildpuzzle.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/onebuildpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OneBuildPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
|
||||
SceneChangeDescription scene;
|
||||
|
||||
if (sceneId == 6519) {
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Pipe joining puzzle under sink");
|
||||
NancySceneState.setEventFlag(425, g_nancy->_true); // EV_Solved_Pipes
|
||||
scene.sceneID = 6520;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else if (sceneId == 2916) {
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Carborosaurus Puzzle");
|
||||
NancySceneState.setEventFlag(424, g_nancy->_true); // EV_Solved_Permit_Task
|
||||
scene.sceneID = 2915;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else {
|
||||
warning("STUB - Nancy 9 One Build Puzzle");
|
||||
}
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/onebuildpuzzle.h
Normal file
51
engines/nancy/action/puzzle/onebuildpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
#define NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Pipe joining puzzle under sink in Nancy 9
|
||||
|
||||
class OneBuildPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
OneBuildPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~OneBuildPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OneBuildPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
634
engines/nancy/action/puzzle/orderingpuzzle.cpp
Normal file
634
engines/nancy/action/puzzle/orderingpuzzle.cpp
Normal file
@@ -0,0 +1,634 @@
|
||||
/* 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 "common/serializer.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/orderingpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OrderingPuzzle::init() {
|
||||
for (uint i = 0; i < _destRects.size(); ++i) {
|
||||
if (i == 0) {
|
||||
_screenPosition = _destRects[i];
|
||||
} else {
|
||||
_screenPosition.extend(_destRects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _overlayDests.size(); ++i) {
|
||||
_screenPosition.extend(_overlayDests[i]);
|
||||
}
|
||||
|
||||
if (!_checkButtonDest.isEmpty()) {
|
||||
_screenPosition.extend(_checkButtonDest);
|
||||
}
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
|
||||
if (_image.hasPalette()) {
|
||||
uint8 palette[256 * 3];
|
||||
_image.grabPalette(palette, 0, 256);
|
||||
_drawSurface.setPalette(palette, 0, 256);
|
||||
}
|
||||
|
||||
setTransparent(true);
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
setVisible(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
bool isPiano = _puzzleType == kPiano;
|
||||
bool isOrderItems = _puzzleType == kOrderItems;
|
||||
bool isKeypad = _puzzleType == kKeypad || _puzzleType == kKeypadTerse;
|
||||
readFilename(stream, _imageName);
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
uint16 numElements = 5;
|
||||
uint16 maxNumElements = 15;
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
// Hardcoded in The Vampire Diaries
|
||||
numElements = maxNumElements = 5;
|
||||
} else {
|
||||
ser.syncAsUint16LE(numElements);
|
||||
}
|
||||
|
||||
switch (_puzzleType) {
|
||||
case kOrderItems :
|
||||
ser.syncAsByte(_hasSecondState);
|
||||
ser.syncAsByte(_itemsStayDown);
|
||||
break;
|
||||
case kPiano :
|
||||
_itemsStayDown = false;
|
||||
break;
|
||||
case kKeypadTerse:
|
||||
// fall through
|
||||
case kKeypad :
|
||||
ser.syncAsByte(_itemsStayDown);
|
||||
ser.syncAsByte(_needButtonToCheckSuccess);
|
||||
readRect(ser, _checkButtonSrc);
|
||||
readRect(ser, _checkButtonDest);
|
||||
maxNumElements = 30;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// nancy7 moved the keypad rects at the end
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6 || !isKeypad) {
|
||||
readRectArray(ser, _down1Rects, numElements, maxNumElements);
|
||||
|
||||
if (isOrderItems) {
|
||||
readRectArray(stream, _up2Rects, numElements, maxNumElements);
|
||||
readRectArray(stream, _down2Rects, numElements, maxNumElements);
|
||||
}
|
||||
|
||||
readRectArray(ser, _destRects, numElements, maxNumElements);
|
||||
|
||||
if (isPiano) {
|
||||
readRectArray(stream, _hotspots, numElements, maxNumElements);
|
||||
} else {
|
||||
_hotspots = _destRects;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPiano && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_specialCursor1Id = stream.readUint16LE();
|
||||
readRect(stream, _specialCursor1Dest);
|
||||
_specialCursor2Id = stream.readUint16LE();
|
||||
readRect(stream, _specialCursor2Dest);
|
||||
}
|
||||
|
||||
uint sequenceLength = 5;
|
||||
ser.syncAsUint16LE(sequenceLength, kGameTypeNancy1);
|
||||
|
||||
if (isKeypad) {
|
||||
ser.syncAsByte(_checkOrder, kGameTypeNancy7);
|
||||
}
|
||||
|
||||
_correctSequence.resize(sequenceLength);
|
||||
uint sizeElem = 1;
|
||||
for (uint i = 0; i < sequenceLength; ++i) {
|
||||
switch (_puzzleType) {
|
||||
case kKeypadTerse:
|
||||
// fall through
|
||||
case kKeypad :
|
||||
// fall through
|
||||
case kOrdering:
|
||||
ser.syncAsByte(_correctSequence[i]);
|
||||
sizeElem = 1;
|
||||
break;
|
||||
case kPiano:
|
||||
ser.syncAsUint16LE(_correctSequence[i]);
|
||||
sizeElem = 2;
|
||||
break;
|
||||
case kOrderItems:
|
||||
// For some reason, OrderItems labels starting from 1
|
||||
ser.syncAsUint16LE(_correctSequence[i]);
|
||||
--_correctSequence[i];
|
||||
sizeElem = 2;
|
||||
break;
|
||||
default:
|
||||
error("OrderingPuzzle::readData(): Unsupported puzzle type %d", _puzzleType);
|
||||
}
|
||||
}
|
||||
ser.skip((maxNumElements - sequenceLength) * sizeElem, kGameTypeNancy1);
|
||||
|
||||
if (isOrderItems) {
|
||||
uint numOverlays = 0;
|
||||
ser.syncAsUint16LE(_state2InvItem);
|
||||
ser.syncAsUint16LE(numOverlays);
|
||||
|
||||
readRectArray(ser, _overlaySrcs, numOverlays);
|
||||
readRectArray(ser, _overlayDests, numOverlays);
|
||||
} else if (isPiano && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
readFilenameArray(stream, _pianoSoundNames, numElements);
|
||||
stream.skip((maxNumElements - numElements) * 33);
|
||||
}
|
||||
|
||||
if (ser.getVersion() > kGameTypeVampire) {
|
||||
_pushDownSound.readNormal(stream);
|
||||
|
||||
if (isOrderItems) {
|
||||
_itemSound.readNormal(stream);
|
||||
_popUpSound.readNormal(stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
_solveExitScene._sceneChange.readData(stream, true);
|
||||
ser.skip(2); // shouldStopRendering
|
||||
ser.syncAsSint16LE(_solveExitScene._flag.label);
|
||||
ser.syncAsByte(_solveExitScene._flag.flag);
|
||||
} else {
|
||||
_solveExitScene.readData(stream);
|
||||
}
|
||||
|
||||
ser.syncAsUint16LE(_solveSoundDelay);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
_exitScene._sceneChange.readData(stream, true);
|
||||
ser.skip(2); // shouldStopRendering
|
||||
ser.syncAsSint16LE(_exitScene._flag.label);
|
||||
ser.syncAsByte(_exitScene._flag.flag);
|
||||
} else {
|
||||
_exitScene.readData(stream);
|
||||
}
|
||||
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
if (isKeypad && g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
if (_puzzleType == kKeypad) {
|
||||
readRectArray(ser, _down1Rects, numElements, maxNumElements);
|
||||
readRectArray(ser, _destRects, numElements, maxNumElements);
|
||||
} else if (_puzzleType == kKeypadTerse) {
|
||||
_down1Rects.resize(numElements);
|
||||
_destRects.resize(numElements);
|
||||
|
||||
// Terse elements are the same size & placed on a grid (in the source image AND on screen)
|
||||
uint16 columns = stream.readUint16LE();
|
||||
stream.skip(2); // rows
|
||||
|
||||
uint16 width = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE();
|
||||
|
||||
Common::Point srcStartPos, srcDist, destStartPos, destDist;
|
||||
|
||||
srcStartPos.x = stream.readUint16LE();
|
||||
srcStartPos.y = stream.readUint16LE();
|
||||
srcDist.x = stream.readUint16LE();
|
||||
srcDist.y = stream.readUint16LE();
|
||||
|
||||
destStartPos.x = stream.readUint16LE();
|
||||
destStartPos.y = stream.readUint16LE();
|
||||
destDist.x = stream.readUint16LE();
|
||||
destDist.y = stream.readUint16LE();
|
||||
|
||||
for (uint i = 0; i < numElements; ++i) {
|
||||
uint x = i % columns;
|
||||
uint y = i / columns;
|
||||
Common::Rect &src = _down1Rects[i];
|
||||
src.left = srcStartPos.x + (x * srcDist.x) + (width * x);
|
||||
src.top = srcStartPos.y + (y * srcDist.y) + (height * y);
|
||||
src.setWidth(width + 1);
|
||||
src.setHeight(height + 1);
|
||||
|
||||
Common::Rect &dest = _destRects[i];
|
||||
dest.left = destStartPos.x + (x * destDist.x) + (width * x);
|
||||
dest.top = destStartPos.y + (y * destDist.y) + (height * y);
|
||||
dest.setWidth(width + 1);
|
||||
dest.setHeight(height + 1);
|
||||
}
|
||||
}
|
||||
|
||||
_hotspots = _destRects;
|
||||
}
|
||||
|
||||
_downItems.resize(numElements, false);
|
||||
_secondStateItems.resize(numElements, false);
|
||||
}
|
||||
|
||||
void OrderingPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
if (g_nancy->getGameType() > kGameTypeVampire) {
|
||||
g_nancy->_sound->loadSound(_pushDownSound);
|
||||
if (_puzzleType == kOrderItems) {
|
||||
g_nancy->_sound->loadSound(_itemSound);
|
||||
g_nancy->_sound->loadSound(_popUpSound);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved: {
|
||||
if (!_itemsStayDown) {
|
||||
// Clear the pushed item
|
||||
if (g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _downItems.size(); ++i) {
|
||||
if (_downItems[i]) {
|
||||
popUp(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool solved = true;
|
||||
|
||||
if (_puzzleType != kPiano) {
|
||||
if (_clickedSequence.size() >= _correctSequence.size()) {
|
||||
bool equal = true;
|
||||
if (_checkOrder) {
|
||||
equal = (_clickedSequence == _correctSequence);
|
||||
} else {
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
bool found = false;
|
||||
for (uint j = 0; j < _clickedSequence.size(); ++j) {
|
||||
if (_correctSequence[i] == _clickedSequence[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Couldn't find one of the items in the correct sequence
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the pressed sequence. If its length is above a certain number,
|
||||
// clear it and start anew
|
||||
if (!equal) {
|
||||
if (_puzzleType != kOrderItems) {
|
||||
uint maxNumPressed = 4;
|
||||
if (g_nancy->getGameType() > kGameTypeVampire) {
|
||||
if (_puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
|
||||
maxNumPressed = _correctSequence.size();
|
||||
} else {
|
||||
maxNumPressed = _correctSequence.size() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_clickedSequence.size() > maxNumPressed) {
|
||||
clearAllElements();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// OrderItems has a slight delay, after which it actually clears
|
||||
if (_clickedSequence.size() == _correctSequence.size()) {
|
||||
if (_solveSoundPlayTime == 0) {
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + 500;
|
||||
} else {
|
||||
if (g_nancy->getTotalPlayTime() > _solveSoundPlayTime) {
|
||||
clearAllElements();
|
||||
_solveSoundPlayTime = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
solved = false;
|
||||
}
|
||||
} else {
|
||||
solved = false;
|
||||
}
|
||||
} else {
|
||||
// Piano puzzle checks only the last few elements
|
||||
if (_clickedSequence.size() < _correctSequence.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Arbitrary number
|
||||
if (_clickedSequence.size() > 30) {
|
||||
_clickedSequence.erase(&_clickedSequence[0], &_clickedSequence[_clickedSequence.size() - _correctSequence.size()]);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
if (_clickedSequence[_clickedSequence.size() - _correctSequence.size() + i] != (int16)_correctSequence[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kKeypad && _needButtonToCheckSuccess) {
|
||||
// KeypadPuzzle moves to the "success" scene regardless whether the puzzle was solved or not,
|
||||
// provided the check button is pressed.
|
||||
if (_checkButtonPressed) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
if (solved) {
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (solved) {
|
||||
if (_puzzleType == kOrderItems) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
// Draw some overlays when solved correctly (OrderItems only)
|
||||
for (uint i = 0; i < _overlaySrcs.size(); ++i) {
|
||||
Common::Rect destRect = _overlayDests[i];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_drawSurface.blitFrom(_image, _overlaySrcs[i], destRect);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
}
|
||||
// fall through
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->stopSound("BUOK");
|
||||
} else {
|
||||
g_nancy->_sound->stopSound(_pushDownSound);
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OrderingPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool canClick = true;
|
||||
if ((_itemsStayDown || _puzzleType == kPiano) && g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
canClick = false;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_needButtonToCheckSuccess && NancySceneState.getViewport().convertViewportToScreen(_checkButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_checkButtonPressed = true;
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
Common::Rect destRect = _checkButtonDest;
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_drawSurface.blitFrom(_image, _checkButtonSrc, destRect);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)_hotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
// Set the custom cursor for nancy8+ PianoPuzzle
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_specialCursor1Dest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_specialCursor1Id);
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_specialCursor2Dest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_specialCursor2Id);
|
||||
} else {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
}
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_puzzleType == kOrderItems) {
|
||||
if (_itemsStayDown && _downItems[i]) {
|
||||
// Button is pressed, OrderItems does not allow for depressing
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getHeldItem() == _state2InvItem) {
|
||||
// We are holding the correct inventory, set the button to its alternate (dusted) state
|
||||
setToSecondState(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kPiano) {
|
||||
// Set the correct sound name for every piano key
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy7) {
|
||||
// In earlier games, the sound name is the base sound + a number
|
||||
if (Common::isDigit(_pushDownSound.name.lastChar())) {
|
||||
_pushDownSound.name.deleteLastChar();
|
||||
}
|
||||
|
||||
_pushDownSound.name.insertChar('0' + i, _pushDownSound.name.size());
|
||||
} else {
|
||||
// Later games added an array of sound names
|
||||
_pushDownSound.name = _pianoSoundNames[i];
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_pushDownSound);
|
||||
}
|
||||
|
||||
if (_puzzleType == kOrdering || _puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
|
||||
// OrderingPuzzle and KeypadPuzzle allow for depressing buttons after they're pressed.
|
||||
// If the button is the last one the player pressed, it is removed from the order.
|
||||
// If not, the sequence is kept wrong and will be reset after enough buttons are pressed
|
||||
for (uint j = 0; j < _clickedSequence.size(); ++j) {
|
||||
if (_clickedSequence[j] == i && _downItems[i] == true) {
|
||||
popUp(i);
|
||||
if (_clickedSequence.back() == i) {
|
||||
_clickedSequence.pop_back();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_clickedSequence.push_back(i);
|
||||
pushDown(i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::String OrderingPuzzle::getRecordTypeName() const {
|
||||
switch (_puzzleType) {
|
||||
case kPiano:
|
||||
return "PianoPuzzle";
|
||||
case kOrderItems:
|
||||
return "OrderItemsPuzzle";
|
||||
case kKeypad:
|
||||
return "KeypadPuzzle";
|
||||
case kKeypadTerse:
|
||||
return "KeypadTersePuzzle";
|
||||
default:
|
||||
return "OrderingPuzzle";
|
||||
}
|
||||
}
|
||||
|
||||
void OrderingPuzzle::pushDown(uint id) {
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->playSound("BUOK");
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
}
|
||||
|
||||
_downItems[id] = true;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _secondStateItems[id] ? _down2Rects[id] : _down1Rects[id], destRect);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::setToSecondState(uint id) {
|
||||
g_nancy->_sound->playSound(_itemSound);
|
||||
|
||||
_secondStateItems[id] = true;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _downItems[id] ? _down2Rects[id] : _up2Rects[id], destRect);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::popUp(uint id) {
|
||||
if (_itemsStayDown) {
|
||||
// Make sure we only play the sound when the buttons don't auto-depress
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->playSound("BUOK");
|
||||
} else {
|
||||
if (_popUpSound.name.size()) {
|
||||
g_nancy->_sound->playSound(_popUpSound);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_downItems[id] = false;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
if (_secondStateItems[id] == false || _up2Rects.size() == 0) {
|
||||
_drawSurface.fillRect(destRect, _drawSurface.getTransparentColor());
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _up2Rects[id], destRect);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::clearAllElements() {
|
||||
for (uint id = 0; id < _downItems.size(); ++id) {
|
||||
popUp(id);
|
||||
}
|
||||
|
||||
_clickedSequence.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
110
engines/nancy/action/puzzle/orderingpuzzle.h
Normal file
110
engines/nancy/action/puzzle/orderingpuzzle.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
#define NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Class implementing several action records of the type where
|
||||
// the player has to press a sequence of buttons in a certain order.
|
||||
// - OrderingPuzzle: The most simple type. Allows for manual depressing of buttons
|
||||
// - PianoPuzzle: Buttons always auto-depress; every button has unique sound file
|
||||
// - OrderItemsPuzzle: Buttons may depress or stay down, but player can't depress manually.
|
||||
// Has second button state that is activated when player is holding a specific item. (see fingerprint keypad puzzle in nancy4)
|
||||
// - KeypadPuzzle: Buttons may auto-depress, stay down, and can be depressed manually by player.
|
||||
// Adds an optional button for manually checking for correct solution, number of possible buttons is 30.
|
||||
// - KeypadPuzzleTerse: Same as above, but data format is shorter, and supports up to 100 buttons
|
||||
class OrderingPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
enum PuzzleType { kOrdering, kPiano, kOrderItems, kKeypad, kKeypadTerse };
|
||||
OrderingPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
|
||||
virtual ~OrderingPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override;
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void pushDown(uint id);
|
||||
void setToSecondState(uint id);
|
||||
void popUp(uint id);
|
||||
void clearAllElements();
|
||||
|
||||
Common::Path _imageName;
|
||||
bool _hasSecondState = false;
|
||||
bool _itemsStayDown = true;
|
||||
bool _needButtonToCheckSuccess = false;
|
||||
bool _checkOrder = true;
|
||||
Common::Rect _checkButtonSrc;
|
||||
Common::Rect _checkButtonDest;
|
||||
Common::Array<Common::Rect> _down1Rects;
|
||||
Common::Array<Common::Rect> _up2Rects;
|
||||
Common::Array<Common::Rect> _down2Rects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<uint16> _correctSequence;
|
||||
|
||||
uint16 _specialCursor1Id = CursorManager::kHotspot;
|
||||
Common::Rect _specialCursor1Dest;
|
||||
uint16 _specialCursor2Id = CursorManager::kHotspot;
|
||||
Common::Rect _specialCursor2Dest;
|
||||
|
||||
Common::Array<Common::String> _pianoSoundNames; // nancy8 and up
|
||||
|
||||
uint16 _state2InvItem = 0;
|
||||
Common::Array<Common::Rect> _overlaySrcs;
|
||||
Common::Array<Common::Rect> _overlayDests;
|
||||
|
||||
Nancy::SoundDescription _pushDownSound;
|
||||
Nancy::SoundDescription _itemSound;
|
||||
Nancy::SoundDescription _popUpSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
Nancy::SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<uint16> _clickedSequence;
|
||||
Common::Array<bool> _downItems;
|
||||
Common::Array<bool> _secondStateItems;
|
||||
Time _solveSoundPlayTime;
|
||||
bool _checkButtonPressed = false;
|
||||
|
||||
PuzzleType _puzzleType;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
236
engines/nancy/action/puzzle/overridelockpuzzle.cpp
Normal file
236
engines/nancy/action/puzzle/overridelockpuzzle.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/viewport.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/overridelockpuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OverrideLockPuzzle::init() {
|
||||
Common::Rect bounds = NancySceneState.getViewport().getBounds();
|
||||
|
||||
_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(bounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
byte num = stream.readByte();
|
||||
_popButtons = stream.readByte();
|
||||
_randomizeLights = stream.readByte();
|
||||
|
||||
readRectArray(stream, _buttonSrcs, num, 10);
|
||||
readRectArray(stream, _buttonDests, num, 10);
|
||||
readRectArray(stream, _hotspots, num, 10);
|
||||
readRectArray(stream, _lightSrcs, num, 10);
|
||||
readRectArray(stream, _lightDests, num, 10);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_wrongSound.readNormal(stream);
|
||||
|
||||
_buttonPopTime = stream.readUint16LE();
|
||||
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
if (g_nancy->getGameType() != kGameTypeNancy5 || NancySceneState.getHeldItem() != 12) {
|
||||
// Hardcoded check for rubber gloves in nancy5
|
||||
NancySceneState.setNoHeldItem();
|
||||
}
|
||||
|
||||
// Set the order of the button presses (always random)
|
||||
// and of the lights (only random on expert difficulty)
|
||||
uint numButtons = _buttonSrcs.size();
|
||||
_buttonOrder.resize(numButtons);
|
||||
_lightsOrder.resize(numButtons);
|
||||
Common::Array<byte> buttonIDs(numButtons);
|
||||
Common::Array<byte> lightIDs(numButtons);
|
||||
for (uint i = 0; i < numButtons; ++i) {
|
||||
buttonIDs[i] = i;
|
||||
lightIDs[i] = i;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < numButtons; ++i) {
|
||||
_buttonOrder[i] = buttonIDs.remove_at(g_nancy->_randomSource->getRandomNumber(buttonIDs.size() - 1));
|
||||
|
||||
if (_randomizeLights == kLightsRandom) {
|
||||
_lightsOrder[i] = lightIDs.remove_at(g_nancy->_randomSource->getRandomNumber(lightIDs.size() - 1));
|
||||
} else {
|
||||
_lightsOrder[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
g_nancy->_sound->loadSound(_wrongSound);
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_timeToPop != 0 && g_nancy->getTotalPlayTime() > _timeToPop) {
|
||||
if (_popButtons == kButtonsPopUp) {
|
||||
drawButton(_lastPushedButton, true);
|
||||
}
|
||||
|
||||
drawLights();
|
||||
|
||||
_lastPushedButton = -1;
|
||||
_timeToPop = 0;
|
||||
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_playerOrder[i] != _buttonOrder[i]) {
|
||||
// Wrong order, reset
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
_playerOrder.clear();
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_wrongSound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_playerOrder.size() == _buttonOrder.size()) {
|
||||
// Solved the puzzle
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
_solveState = kSolved;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kSolved:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveExitScene.execute();
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
g_nancy->_sound->stopSound(_wrongSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::handleInput(NancyInput &input) {
|
||||
if ((_state != kRun && _solveState != kNotSolved) || _timeToPop != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the exit hotspot
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _buttonOrder.size(); ++i) {
|
||||
bool hotspotIsInactive = false;
|
||||
for (uint j = 0; j < _playerOrder.size(); ++j) {
|
||||
if (_playerOrder[j] == i) {
|
||||
hotspotIsInactive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hotspotIsInactive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_buttonSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
drawButton(i, false);
|
||||
_lastPushedButton = i;
|
||||
_timeToPop = g_nancy->getTotalPlayTime() + _buttonPopTime;
|
||||
_playerOrder.push_back(i);
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::drawButton(uint buttonID, bool clear) {
|
||||
if (clear) {
|
||||
_drawSurface.fillRect(_buttonDests[buttonID], _drawSurface.getTransparentColor());
|
||||
return;
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[buttonID], _buttonDests[buttonID]);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::drawLights() {
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_randomizeLights == kLightsCircular) {
|
||||
_drawSurface.blitFrom(_image, _lightSrcs[i], _lightDests[i]);
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _lightSrcs[_lightsOrder[i]], _lightDests[_lightsOrder[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
91
engines/nancy/action/puzzle/overridelockpuzzle.h
Normal file
91
engines/nancy/action/puzzle/overridelockpuzzle.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
#define NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class OverrideLockPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
static const byte kButtonsStayDown = 1;
|
||||
static const byte kButtonsPopUp = 2;
|
||||
|
||||
static const byte kLightsCircular = 3;
|
||||
static const byte kLightsRandom = 4;
|
||||
|
||||
OverrideLockPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~OverrideLockPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OverrideLockPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawButton(uint buttonID, bool clear);
|
||||
void drawLights();
|
||||
|
||||
enum SolveState { kNotSolved, kSolved };
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
byte _popButtons = kButtonsStayDown;
|
||||
byte _randomizeLights = kLightsCircular;
|
||||
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<Common::Rect> _lightSrcs;
|
||||
Common::Array<Common::Rect> _lightDests;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SoundDescription _wrongSound;
|
||||
|
||||
Time _buttonPopTime;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
Common::Array<byte> _buttonOrder;
|
||||
Common::Array<byte> _lightsOrder;
|
||||
Common::Array<byte> _playerOrder;
|
||||
|
||||
Time _timeToPop;
|
||||
SolveState _solveState = kNotSolved;
|
||||
int8 _lastPushedButton = -1;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
261
engines/nancy/action/puzzle/passwordpuzzle.cpp
Normal file
261
engines/nancy/action/puzzle/passwordpuzzle.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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/action/puzzle/passwordpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
PasswordPuzzle::~PasswordPuzzle() {
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void PasswordPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer s(&stream, nullptr);
|
||||
s.setVersion(g_nancy->getGameType());
|
||||
|
||||
s.syncAsUint16LE(_fontID);
|
||||
s.syncAsUint16LE(_cursorBlinkTime);
|
||||
readRect(s, _nameBounds);
|
||||
readRect(s, _passwordBounds);
|
||||
readRect(s, _screenPosition);
|
||||
|
||||
uint numNames = 1;
|
||||
uint numPasswords = 1;
|
||||
char buf[33];
|
||||
uint fieldSize = s.getVersion() <= kGameTypeNancy5 ? 20 : 33; // nancy6 changed the size of text fields to 33
|
||||
|
||||
s.syncAsUint16LE(numNames, kGameTypeNancy4);
|
||||
_names.resize(numNames);
|
||||
for (uint i = 0; i < numNames; ++i) {
|
||||
stream.read(buf, fieldSize);
|
||||
buf[fieldSize - 1] = '\0';
|
||||
_names[i] = buf;
|
||||
}
|
||||
s.skip((5 - numNames) * fieldSize, kGameTypeNancy4);
|
||||
|
||||
s.syncAsUint16LE(numPasswords, kGameTypeNancy4);
|
||||
_passwords.resize(numPasswords);
|
||||
for (uint i = 0; i < numPasswords; ++i) {
|
||||
stream.read(buf, fieldSize);
|
||||
buf[19] = '\0';
|
||||
_passwords[i] = buf;
|
||||
}
|
||||
s.skip((5 - numPasswords) * fieldSize, kGameTypeNancy4);
|
||||
|
||||
_maxStringLength = g_nancy->getGameType() < kGameTypeNancy6 ? 12 : 31;
|
||||
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
_failExitScene.readData(stream);
|
||||
_failSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_input->setVKEnabled(true);
|
||||
_nextBlinkTime = g_nancy->getTotalPlayTime() + _cursorBlinkTime;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved: {
|
||||
Common::String &activeField = _passwordFieldIsActive ? _playerPasswordInput : _playerNameInput;
|
||||
Common::Array<Common::String> &correctAnswers = _passwordFieldIsActive ? _passwords : _names;
|
||||
Time currentTime = g_nancy->getTotalPlayTime();
|
||||
|
||||
if (_playerHasHitReturn) {
|
||||
_playerHasHitReturn = false;
|
||||
|
||||
if (activeField.lastChar() == '-') {
|
||||
activeField.deleteLastChar();
|
||||
drawText();
|
||||
}
|
||||
|
||||
bool solvedCurrentInput = false;
|
||||
if (correctAnswers.size()) {
|
||||
for (uint i = 0; i < correctAnswers.size(); ++i) {
|
||||
if (activeField.equalsIgnoreCase(correctAnswers[i])) {
|
||||
solvedCurrentInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
solvedCurrentInput = true;
|
||||
}
|
||||
|
||||
if (solvedCurrentInput) {
|
||||
if (_passwordFieldIsActive || _passwords.size() == 0) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kSolved;
|
||||
} else {
|
||||
_passwordFieldIsActive = true;
|
||||
}
|
||||
} else {
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
_solveState = kFailed;
|
||||
}
|
||||
} else if (currentTime >= _nextBlinkTime) {
|
||||
_nextBlinkTime = currentTime + _cursorBlinkTime;
|
||||
|
||||
if (activeField.size() && activeField.lastChar() == '-') {
|
||||
activeField.deleteLastChar();
|
||||
} else {
|
||||
activeField += '-';
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kFailed:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kSolved:
|
||||
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 kFailed:
|
||||
_failExitScene.execute();
|
||||
break;
|
||||
case kSolved:
|
||||
_solveExitScene.execute();
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordPuzzle::onPause(bool paused) {
|
||||
g_nancy->_input->setVKEnabled(!paused);
|
||||
RenderActionRecord::onPause(paused);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
|
||||
Common::KeyState &key = input.otherKbdInput[i];
|
||||
Common::String &activeField = _passwordFieldIsActive ? _playerPasswordInput : _playerNameInput;
|
||||
if (key.keycode == Common::KEYCODE_BACKSPACE) {
|
||||
if (activeField.size() && activeField.lastChar() == '-' ? activeField.size() > 1 : true) {
|
||||
if (activeField.lastChar() == '-') {
|
||||
activeField.deleteChar(activeField.size() - 2);
|
||||
} else {
|
||||
activeField.deleteLastChar();
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
} else if (key.keycode == Common::KEYCODE_RETURN || key.keycode == Common::KEYCODE_KP_ENTER) {
|
||||
_playerHasHitReturn = true;
|
||||
} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
|
||||
if (activeField.size() && activeField.lastChar() == '-') {
|
||||
if (activeField.size() <= _maxStringLength + 1) {
|
||||
activeField.deleteLastChar();
|
||||
activeField += key.ascii;
|
||||
activeField += '-';
|
||||
}
|
||||
} else {
|
||||
if (activeField.size() <= _maxStringLength) {
|
||||
activeField += key.ascii;
|
||||
}
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordPuzzle::drawText() {
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
const Graphics::Font *font = g_nancy->_graphics->getFont(_fontID);
|
||||
|
||||
Common::Rect bounds = _nameBounds;
|
||||
bounds = NancySceneState.getViewport().convertViewportToScreen(bounds);
|
||||
bounds = convertToLocal(bounds);
|
||||
Common::Point destPoint(bounds.left, bounds.bottom + 1 - font->getFontHeight());
|
||||
font->drawString(&_drawSurface, _playerNameInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
bounds = _passwordBounds;
|
||||
bounds = NancySceneState.getViewport().convertViewportToScreen(bounds);
|
||||
bounds = convertToLocal(bounds);
|
||||
destPoint.x = bounds.left;
|
||||
destPoint.y = bounds.bottom + 1 - font->getFontHeight();
|
||||
font->drawString(&_drawSurface, _playerPasswordInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
76
engines/nancy/action/puzzle/passwordpuzzle.h
Normal file
76
engines/nancy/action/puzzle/passwordpuzzle.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
#define NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class PasswordPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kFailed, kSolved };
|
||||
PasswordPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~PasswordPuzzle();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool paused) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
uint16 _fontID = 0;
|
||||
uint16 _cursorBlinkTime = 500;
|
||||
Common::Rect _nameBounds;
|
||||
Common::Rect _passwordBounds;
|
||||
// _screenPosition 0x24
|
||||
Common::Array<Common::String> _names;
|
||||
Common::Array<Common::String> _passwords;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _failExitScene;
|
||||
SoundDescription _failSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Common::String _playerNameInput;
|
||||
Common::String _playerPasswordInput;
|
||||
Time _nextBlinkTime;
|
||||
bool _passwordFieldIsActive = false;
|
||||
bool _playerHasHitReturn = false;
|
||||
SolveState _solveState = kNotSolved;
|
||||
|
||||
uint _maxStringLength = 0;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PasswordPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawText();
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
365
engines/nancy/action/puzzle/peepholepuzzle.cpp
Normal file
365
engines/nancy/action/puzzle/peepholepuzzle.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
/* 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
|
||||
102
engines/nancy/action/puzzle/peepholepuzzle.h
Normal file
102
engines/nancy/action/puzzle/peepholepuzzle.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
#define NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/autotext.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Action record that, despite what its name suggests, is mostly used
|
||||
// to render Nancy's diary in nancy6 and up.
|
||||
class PeepholePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
PeepholePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~PeepholePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PeepholePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawInner();
|
||||
void checkButtons();
|
||||
|
||||
Common::Path _innerImageName;
|
||||
Common::Path _buttonsImageName;
|
||||
|
||||
uint16 _transparency = 0;
|
||||
|
||||
Common::Rect _innerBounds;
|
||||
Common::Rect _startSrc;
|
||||
Common::Rect _dest;
|
||||
|
||||
// Order: up, down, left, right
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Rect> _buttonDisabledSrcs;
|
||||
|
||||
byte _pixelsToScroll = 0;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _innerImage;
|
||||
Graphics::ManagedSurface _buttonsImage;
|
||||
|
||||
Common::Rect _currentSrc;
|
||||
int _pressedButton = -1;
|
||||
uint32 _pressStart = 0;
|
||||
Common::Array<bool> _disabledButtons = Common::Array<bool>(4, false);
|
||||
};
|
||||
|
||||
// Combines the Peephole with the Autotext used to supply its text data
|
||||
// Implementing this with diamond-shaped multiple inheritance is bad,
|
||||
// but so is the original design, where a Peephole is constructed
|
||||
// on the fly and replaces the TextScroll/AutotextEntryList
|
||||
class TextScroll : public Autotext, public PeepholePuzzle {
|
||||
public:
|
||||
TextScroll(bool isEntryList) : _isEntryList(isEntryList) {}
|
||||
|
||||
void init() override;
|
||||
void execute() override { PeepholePuzzle::execute(); }
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return _isEntryList ? "AutotextEntryList" : "TextScroll"; }
|
||||
void readExtraData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
bool _isEntryList;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
96
engines/nancy/action/puzzle/quizpuzzle.cpp
Normal file
96
engines/nancy/action/puzzle/quizpuzzle.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/quizpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void QuizPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void QuizPuzzle::execute() {
|
||||
// TODO
|
||||
|
||||
if (g_nancy->getGameType() == kGameTypeNancy8) {
|
||||
warning("STUB - Nancy 8 Quiz Puzzle");
|
||||
} else if (g_nancy->getGameType() == kGameTypeNancy9) {
|
||||
const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
|
||||
if (sceneId == 6450) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's quiz, page 1");
|
||||
// Set the puzzle event flags to flag it as done
|
||||
NancySceneState.setEventFlag(59, g_nancy->_true); // EV_Answered_SQ_Q06
|
||||
} else if (sceneId == 6451) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's quiz, page 2");
|
||||
// Set the puzzle event flags to flag it as done
|
||||
NancySceneState.setEventFlag(61, g_nancy->_true); // EV_Answered_SQ_Q09
|
||||
} else if (sceneId == 6342) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(410, g_nancy->_true); // EV_Solved_GPS_Beach
|
||||
} else if (sceneId == 6344) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint - cache A");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(411, g_nancy->_true); // EV_Solved_GPS_CacheA
|
||||
} else if (sceneId == 6345) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint - cache B");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(412, g_nancy->_true); // EV_Solved_GPS_CacheB
|
||||
} else if (sceneId == 6431) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Hilda Swenson's letter");
|
||||
NancySceneState.setEventFlag(179, g_nancy->_true); // EV_Hilda_Said_Objects
|
||||
} else if (sceneId == 6443) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's chess problem");
|
||||
NancySceneState.setEventFlag(119, g_nancy->_true); // EV_Finished_Chess_Quiz
|
||||
} else if (sceneId == 4184) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Lighthouse Morse code");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4190;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle");
|
||||
}
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void QuizPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void QuizPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
50
engines/nancy/action/puzzle/quizpuzzle.h
Normal file
50
engines/nancy/action/puzzle/quizpuzzle.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_QUIZPUZZLE_H
|
||||
#define NANCY_ACTION_QUIZPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Stenography tutorial in Nancy 8
|
||||
|
||||
class QuizPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
QuizPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~QuizPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "QuizPuzzle"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_QUIZPUZZLE_H
|
||||
2089
engines/nancy/action/puzzle/raycastpuzzle.cpp
Normal file
2089
engines/nancy/action/puzzle/raycastpuzzle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
139
engines/nancy/action/puzzle/raycastpuzzle.h
Normal file
139
engines/nancy/action/puzzle/raycastpuzzle.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
#define NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RCPR;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RaycastDeferredLoader;
|
||||
class RaycastLevelBuilder;
|
||||
|
||||
// Action record implementing nancy3's maze minigame
|
||||
class RaycastPuzzle : public RenderActionRecord {
|
||||
friend class RaycastDeferredLoader;
|
||||
friend class RaycastLevelBuilder;
|
||||
public:
|
||||
RaycastPuzzle() : RenderActionRecord(7), _map(7) {}
|
||||
~RaycastPuzzle() override;
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool pause) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
void updateGraphics() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RaycastPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void validateMap();
|
||||
|
||||
void createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, const Common::Path &textureName);
|
||||
|
||||
void drawMap();
|
||||
void updateMap();
|
||||
void drawMaze();
|
||||
void clearZBuffer();
|
||||
|
||||
void checkSwitch();
|
||||
void checkExit();
|
||||
|
||||
uint16 _mapWidth = 0;
|
||||
uint16 _mapHeight = 0;
|
||||
byte _wallHeight = 0;
|
||||
|
||||
Common::String _switchSoundName;
|
||||
uint16 _switchSoundChannelID = 0;
|
||||
Common::String _unknownSoundName;
|
||||
uint16 _unknownSoundChannelID = 0;
|
||||
SoundDescription _dummySound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
Common::Array<uint32> _wallMap, _infoMap;
|
||||
Common::Array<int16> _floorMap, _ceilingMap;
|
||||
Common::Array<uint16> _wallLightMap, _floorCeilingLightMap, _heightMap;
|
||||
Common::Array<uint16> _wallLightMapBackup, _floorCeilingLightMapBackup;
|
||||
|
||||
uint16 _mapFullWidth = 0;
|
||||
uint16 _mapFullHeight = 0;
|
||||
|
||||
RenderObject _map;
|
||||
Graphics::ManagedSurface _mapBaseSurface;
|
||||
|
||||
double _pi = 3.141592653589793;
|
||||
uint _fov = 192;
|
||||
Common::Array<float> _sinTable, _cosTable;
|
||||
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _wallTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _specialWallTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _ceilingTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _floorTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _exitFloorTextures;
|
||||
|
||||
Common::Array<int32> _wallCastColumnAngles;
|
||||
Common::Array<byte> _zBuffer;
|
||||
byte _lastZDepth = 0;
|
||||
Common::Array<float> _depthBuffer;
|
||||
int32 _leftmostAngle = -1;
|
||||
int32 _rightmostAngle = -1;
|
||||
|
||||
// Improvement: we store position as float for smoother movement
|
||||
float _playerX = -1; // Player position with precision 1/128th of cell width/height
|
||||
float _playerY = -1;
|
||||
int32 _playerRotation = 0; // Rotation of player (0 - 4096)
|
||||
uint32 _playerAltitude = 88; // Z position of "camera"; only modified in god mode
|
||||
|
||||
float _rotationSingleStep = 4096.0 / (_pi * 2);
|
||||
float _maxWorldDistance = 0.0;
|
||||
|
||||
uint32 _lastMovementTime = 0;
|
||||
int _lastMouseX = -1;
|
||||
|
||||
uint32 _nextSlowdownMovementTime = 0;
|
||||
byte _slowdownFramesLeft = 0;
|
||||
int32 _slowdownDeltaX = -1;
|
||||
int32 _slowdownDeltaY = -1;
|
||||
|
||||
Common::Array<byte> _lightSwitchIDs;
|
||||
Common::Array<Common::Point> _lightSwitchPositions;
|
||||
Common::Array<bool> _lightSwitchStates;
|
||||
int _lightSwitchPlayerIsOn = -1;
|
||||
|
||||
const RCPR *_puzzleData = nullptr;
|
||||
Common::SharedPtr<RaycastDeferredLoader> _loaderPtr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
343
engines/nancy/action/puzzle/riddlepuzzle.cpp
Normal file
343
engines/nancy/action/puzzle/riddlepuzzle.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/riddlepuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
RiddlePuzzle::~RiddlePuzzle() {
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
}
|
||||
|
||||
void RiddlePuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void RiddlePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (RiddlePuzzleData *)NancySceneState.getPuzzleData(RiddlePuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
_viewportTextFontID = stream.readUint16LE();
|
||||
_textboxTextFontID = stream.readUint16LE();
|
||||
_cursorBlinkTime = stream.readUint16LE();
|
||||
readRect(stream, _screenPosition);
|
||||
_typeSound.readNormal(stream);
|
||||
_eraseSound.readNormal(stream);
|
||||
_enterSound.readNormal(stream);
|
||||
_successSceneChange.readData(stream);
|
||||
_successSound.readNormal(stream);
|
||||
_exitSceneChange.readData(stream);
|
||||
_exitSound.readNormal(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
_riddles.resize(stream.readUint16LE()) ;
|
||||
stream.skip(4);
|
||||
|
||||
char buf[128];
|
||||
for (uint i = 0; i < _riddles.size(); ++i) {
|
||||
Riddle &riddle = _riddles[i];
|
||||
|
||||
stream.read(buf, 128);
|
||||
buf[127] = '\0';
|
||||
riddle.text = buf;
|
||||
riddle.sound.readNormal(stream);
|
||||
|
||||
for (uint j = 0; j < 8; ++j) {
|
||||
stream.read(buf, 20);
|
||||
buf[19] = '\0';
|
||||
Common::String answer = buf;
|
||||
if (!answer.empty()) {
|
||||
riddle.answers.push_back(answer);
|
||||
}
|
||||
}
|
||||
|
||||
riddle.sceneIncorrect.readData(stream);
|
||||
riddle.soundIncorrect.readNormal(stream);
|
||||
riddle.sceneCorrect.readData(stream);
|
||||
riddle.soundCorrect.readNormal(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
_puzzleState = (RiddlePuzzleData *)NancySceneState.getPuzzleData(RiddlePuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
_nextBlinkTime = g_nancy->getTotalPlayTime() + _cursorBlinkTime;
|
||||
|
||||
g_nancy->_sound->loadSound(_typeSound);
|
||||
g_nancy->_sound->loadSound(_eraseSound);
|
||||
g_nancy->_sound->loadSound(_enterSound);
|
||||
|
||||
// Make a list of non-answered riddle IDs
|
||||
Common::Array<byte> availableIDs;
|
||||
for (uint i = 0; i < _riddles.size(); ++i) {
|
||||
bool isAlreadySolved = false;
|
||||
for (auto id : _puzzleState->solvedRiddleIDs) {
|
||||
if (i == id) {
|
||||
isAlreadySolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAlreadySolved) {
|
||||
availableIDs.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (availableIDs.size() == 0) {
|
||||
_solveState = kSolvedAll;
|
||||
_state = kRun;
|
||||
break;
|
||||
} else {
|
||||
if (_puzzleState->incorrectRiddleID != -1) {
|
||||
_riddleID = _puzzleState->incorrectRiddleID;
|
||||
} else {
|
||||
_riddleID = availableIDs[g_nancy->_randomSource->getRandomNumber(availableIDs.size() - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].sound);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].sound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().setOverrideFont(_textboxTextFontID);
|
||||
NancySceneState.getTextbox().addTextLine(_riddles[_riddleID].text);
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_riddles[_riddleID].sound)) {
|
||||
_solveState = kNotSolved;
|
||||
g_nancy->_input->setVKEnabled(true);
|
||||
}
|
||||
|
||||
break;
|
||||
case kNotSolved: {
|
||||
Time currentTime = g_nancy->getTotalPlayTime();
|
||||
|
||||
if (_playerHasHitReturn) {
|
||||
_playerHasHitReturn = false;
|
||||
|
||||
if (_playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteLastChar();
|
||||
drawText();
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_enterSound)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (Common::String &answer : _riddles[_riddleID].answers) {
|
||||
if (_playerInput.equalsIgnoreCase(answer)) {
|
||||
// Solved a riddle
|
||||
_puzzleState->solvedRiddleIDs.push_back(_riddleID);
|
||||
|
||||
if (_puzzleState->solvedRiddleIDs.size() == _riddles.size()) {
|
||||
// Solved all riddles
|
||||
g_nancy->_sound->loadSound(_successSound);
|
||||
g_nancy->_sound->playSound(_successSound);
|
||||
_solveState = kSolvedAll;
|
||||
_state = kActionTrigger;
|
||||
|
||||
break;
|
||||
} else {
|
||||
// Still have riddles to solve
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].soundCorrect);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].soundCorrect);
|
||||
_solveState = kSolvedOne;
|
||||
_state = kActionTrigger;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
// Did not solve a riddle
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].soundIncorrect);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].soundIncorrect);
|
||||
_solveState = kFailed;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
} else if (currentTime >= _nextBlinkTime) {
|
||||
_nextBlinkTime = currentTime + _cursorBlinkTime;
|
||||
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteLastChar();
|
||||
} else {
|
||||
_playerInput += '-';
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger: {
|
||||
SoundDescription *sound = nullptr;
|
||||
SceneChangeWithFlag *sceneChange = nullptr;
|
||||
_puzzleState->incorrectRiddleID = -1;
|
||||
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
sound = &_exitSound;
|
||||
sceneChange = &_exitSceneChange;
|
||||
|
||||
break;
|
||||
case kFailed:
|
||||
sound = &_riddles[_riddleID].soundIncorrect;
|
||||
sceneChange = &_riddles[_riddleID].sceneIncorrect;
|
||||
_puzzleState->incorrectRiddleID = _riddleID;
|
||||
|
||||
break;
|
||||
case kSolvedOne:
|
||||
sound = &_riddles[_riddleID].soundCorrect;
|
||||
sceneChange = &_riddles[_riddleID].sceneCorrect;
|
||||
|
||||
break;
|
||||
case kSolvedAll:
|
||||
sound = &_successSound;
|
||||
sceneChange = &_successSceneChange;
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(*sound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(*sound);
|
||||
g_nancy->_sound->stopSound(_typeSound);
|
||||
g_nancy->_sound->stopSound(_eraseSound);
|
||||
g_nancy->_sound->stopSound(_enterSound);
|
||||
|
||||
sceneChange->execute();
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::onPause(bool paused) {
|
||||
g_nancy->_input->setVKEnabled(!paused);
|
||||
RenderActionRecord::onPause(paused);
|
||||
}
|
||||
|
||||
void RiddlePuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
|
||||
Common::KeyState &key = input.otherKbdInput[i];
|
||||
if (key.keycode == Common::KEYCODE_BACKSPACE) {
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-' ? _playerInput.size() > 1 : true) {
|
||||
if (_playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteChar(_playerInput.size() - 2);
|
||||
} else {
|
||||
_playerInput.deleteLastChar();
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_eraseSound);
|
||||
|
||||
drawText();
|
||||
}
|
||||
} else if (key.keycode == Common::KEYCODE_RETURN || key.keycode == Common::KEYCODE_KP_ENTER) {
|
||||
if (_playerInput.size() == 0 ||
|
||||
(_playerInput.size() == 1 && _playerInput.lastChar() == '-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_playerHasHitReturn = true;
|
||||
g_nancy->_sound->playSound(_enterSound);
|
||||
} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-') {
|
||||
if (_playerInput.size() <= 16) {
|
||||
_playerInput.deleteLastChar();
|
||||
_playerInput += key.ascii;
|
||||
_playerInput += '-';
|
||||
g_nancy->_sound->playSound(_typeSound);
|
||||
drawText();
|
||||
}
|
||||
} else {
|
||||
if (_playerInput.size() <= 15) {
|
||||
_playerInput += key.ascii;
|
||||
g_nancy->_sound->playSound(_typeSound);
|
||||
drawText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::drawText() {
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
const Graphics::Font *font = g_nancy->_graphics->getFont(_viewportTextFontID);
|
||||
|
||||
Common::Rect bounds = getBounds();
|
||||
Common::Point destPoint(bounds.left, bounds.bottom - font->getFontHeight());
|
||||
font->drawString(&_drawSurface, _playerInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
86
engines/nancy/action/puzzle/riddlepuzzle.h
Normal file
86
engines/nancy/action/puzzle/riddlepuzzle.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
#define NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RiddlePuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RiddlePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kWaitForSound, kNotSolved, kFailed, kSolvedOne, kSolvedAll };
|
||||
RiddlePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RiddlePuzzle();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool paused) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
struct Riddle {
|
||||
Common::String text;
|
||||
SoundDescription sound;
|
||||
Common::Array<Common::String> answers;
|
||||
SceneChangeWithFlag sceneIncorrect;
|
||||
SoundDescription soundIncorrect;
|
||||
SceneChangeWithFlag sceneCorrect;
|
||||
SoundDescription soundCorrect;
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return "RiddlePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawText();
|
||||
|
||||
uint16 _viewportTextFontID = 0;
|
||||
uint16 _textboxTextFontID = 0;
|
||||
Time _cursorBlinkTime;
|
||||
SoundDescription _typeSound;
|
||||
SoundDescription _eraseSound;
|
||||
SoundDescription _enterSound;
|
||||
SceneChangeWithFlag _successSceneChange;
|
||||
SoundDescription _successSound;
|
||||
SceneChangeWithFlag _exitSceneChange;
|
||||
SoundDescription _exitSound;
|
||||
Common::Rect _exitHotspot;
|
||||
Common::Array<Riddle> _riddles;
|
||||
|
||||
Time _nextBlinkTime;
|
||||
SolveState _solveState = kWaitForSound;
|
||||
bool _playerHasHitReturn = false;
|
||||
Common::String _playerInput;
|
||||
uint _riddleID = 0;
|
||||
RiddlePuzzleData *_puzzleState = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
466
engines/nancy/action/puzzle/rippedletterpuzzle.cpp
Normal file
466
engines/nancy/action/puzzle/rippedletterpuzzle.cpp
Normal file
@@ -0,0 +1,466 @@
|
||||
/* 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
|
||||
99
engines/nancy/action/puzzle/rippedletterpuzzle.h
Normal file
99
engines/nancy/action/puzzle/rippedletterpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
#define NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RippedLetterPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RippedLetterPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
enum RotationType { kRotationNone = 0, kRotation90 = 1, kRotation180 = 2 };
|
||||
RippedLetterPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RippedLetterPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
|
||||
Common::Rect _rotateHotspot;
|
||||
Common::Rect _takeHotspot;
|
||||
Common::Rect _dropHotspot;
|
||||
|
||||
RotationType _rotationType = kRotation90;
|
||||
|
||||
Common::Array<int8> _initOrder;
|
||||
Common::Array<byte> _initRotations;
|
||||
Common::Array<int8> _solveOrder;
|
||||
Common::Array<byte> _solveRotations;
|
||||
Common::Array<int8> _solveOrderAlt;
|
||||
Common::Array<byte> _solveRotationsAlt;
|
||||
Common::Array<Common::Array<byte>> _doubles;
|
||||
bool _useAltSolution = false;
|
||||
|
||||
bool _useCustomPickUpTile = false;
|
||||
Common::Rect _customPickUpTileSrc;
|
||||
|
||||
SoundDescription _takeSound;
|
||||
SoundDescription _dropSound;
|
||||
SoundDescription _rotateSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
int16 _customCursorID = -1;
|
||||
|
||||
Misc::MouseFollowObject _pickedUpPiece;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
SolveState _solveState = kNotSolved;
|
||||
RippedLetterPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RippedLetterPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawPiece(const uint pos, const byte rotation, const int pieceID = -1);
|
||||
bool checkOrder(bool useAlt);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif //NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
219
engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
Normal file
219
engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
/* 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 "common/random.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/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void RotatingLockPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint numDials = stream.readUint16LE();
|
||||
|
||||
_srcRects.reserve(10);
|
||||
for (uint i = 0; i < 10; ++i) {
|
||||
_srcRects.push_back(Common::Rect());
|
||||
readRect(stream, _srcRects.back());
|
||||
}
|
||||
|
||||
_destRects.reserve(numDials);
|
||||
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);
|
||||
|
||||
_upHotspots.reserve(numDials);
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_upHotspots.push_back(Common::Rect());
|
||||
readRect(stream, _upHotspots.back());
|
||||
}
|
||||
|
||||
_downHotspots.reserve(numDials);
|
||||
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);
|
||||
|
||||
_correctSequence.reserve(numDials);
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_correctSequence.push_back(stream.readByte());
|
||||
}
|
||||
|
||||
stream.skip(8 - numDials);
|
||||
|
||||
_clickSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
_currentSequence.push_back(g_nancy->_randomSource->getRandomNumber(9));
|
||||
drawDial(i);
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_clickSound);
|
||||
g_nancy->_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;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
// fall through
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case kActionTrigger:
|
||||
g_nancy->_sound->stopSound(_clickSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _upHotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_upHotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
|
||||
_currentSequence[i] = ++_currentSequence[i] > 9 ? 0 : _currentSequence[i];
|
||||
drawDial(i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _downHotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_downHotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
|
||||
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
|
||||
70
engines/nancy/action/puzzle/rotatinglockpuzzle.h
Normal file
70
engines/nancy/action/puzzle/rotatinglockpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
#define NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class RotatingLockPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
RotatingLockPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RotatingLockPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _upHotspots;
|
||||
Common::Array<Common::Rect> _downHotspots;
|
||||
Common::Array<byte> _correctSequence;
|
||||
Nancy::SoundDescription _clickSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
Nancy::SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<byte> _currentSequence;
|
||||
Time _solveSoundPlayTime;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RotatingLockPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawDial(uint id);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
321
engines/nancy/action/puzzle/safedialpuzzle.cpp
Normal file
321
engines/nancy/action/puzzle/safedialpuzzle.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
/* 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 "common/random.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/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/safedialpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SafeDialPuzzle::init() {
|
||||
g_nancy->_resource->loadImage(_imageName1, _image1);
|
||||
g_nancy->_resource->loadImage(_imageName2, _image2);
|
||||
g_nancy->_resource->loadImage(_resetImageName, _resetImage);
|
||||
|
||||
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);
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::updateGraphics() {
|
||||
if (_animState == kSelect && (_state == kActionTrigger ? _nextAnim - 500 : _nextAnim) < g_nancy->getTotalPlayTime()) {
|
||||
_drawSurface.fillRect(_arrowDest, _drawSurface.getTransparentColor());
|
||||
_animState = kNone;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_animState == kSpin && _nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
drawDialFrame(_current * (1 + _numInbetweens));
|
||||
_animState = kNone;
|
||||
}
|
||||
|
||||
if (_animState == kReset && _nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
if (!_resetImageName.empty()) {
|
||||
_animState = kResetAnim;
|
||||
} else {
|
||||
_animState = kNone;
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_resetSound);
|
||||
}
|
||||
|
||||
if (_animState == kResetAnim) {
|
||||
// Framerate-dependent animation. We're restricting the engine to ~60fps so it shouldn't be too fast
|
||||
_drawSurface.blitFrom(_resetImage, _resetDialSrcs[_current % _resetDialSrcs.size()], _dialDest);
|
||||
++_current;
|
||||
if (_current >= _resetDialSrcs.size() * _resetTurns) {
|
||||
_animState = kNone;
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
}
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName1);
|
||||
readFilename(stream, _imageName2);
|
||||
readFilename(stream, _resetImageName);
|
||||
|
||||
_numInbetweens = (!_imageName2.empty() ? 1 : 0);
|
||||
|
||||
uint16 num = 10;
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy4) {
|
||||
num = stream.readUint16LE();
|
||||
_enableWraparound = stream.readByte();
|
||||
}
|
||||
|
||||
readRect(stream, _dialDest);
|
||||
readRectArray(stream, _dialSrcs, num * (1 + _numInbetweens), 20);
|
||||
|
||||
readRect(stream, _resetDest);
|
||||
readRect(stream, _resetSrc);
|
||||
readRect(stream, _arrowDest);
|
||||
readRect(stream, _arrowSrc);
|
||||
|
||||
readRectArray(stream, _resetDialSrcs, 10);
|
||||
|
||||
_resetTurns = stream.readUint16LE();
|
||||
|
||||
uint16 solveSize = stream.readUint16LE();
|
||||
_correctSequence.resize(solveSize);
|
||||
for (uint i = 0; i < solveSize; ++i) {
|
||||
_correctSequence[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((10 - solveSize) * 2);
|
||||
|
||||
readRect(stream, _ccwHotspot);
|
||||
readRect(stream, _cwHotspot);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy4) {
|
||||
_useMoveArrows = stream.readByte();
|
||||
}
|
||||
|
||||
if (_useMoveArrows) {
|
||||
// Swap the two hotspots
|
||||
Common::Rect temp = _cwHotspot;
|
||||
_cwHotspot = _ccwHotspot;
|
||||
_ccwHotspot = temp;
|
||||
}
|
||||
|
||||
_spinSound.readNormal(stream);
|
||||
_selectSound.readNormal(stream);
|
||||
_resetSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_spinSound);
|
||||
g_nancy->_sound->loadSound(_selectSound);
|
||||
g_nancy->_sound->loadSound(_resetSound);
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (!g_nancy->_sound->isSoundPlaying(_selectSound) && g_nancy->getTotalPlayTime() > _nextAnim) {
|
||||
if (_playerSequence == _correctSequence) {
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 1000 * _solveSoundDelay;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (_nextAnim == 0) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (_nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_nextAnim = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_spinSound);
|
||||
g_nancy->_sound->stopSound(_selectSound);
|
||||
g_nancy->_sound->stopSound(_resetSound);
|
||||
|
||||
finishExecution();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _playerSequence == _correctSequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_ccwHotspot).contains(input.mousePos)) {
|
||||
if (!_enableWraparound && _current == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveLeft : CursorManager::kRotateCCW);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
|
||||
_animState != kReset && _animState != kResetAnim) {
|
||||
if (_current == 0) {
|
||||
_current = _dialSrcs.size() / (1 + _numInbetweens) - 1;
|
||||
} else {
|
||||
--_current;
|
||||
}
|
||||
|
||||
drawDialFrame(_current * (1 + _numInbetweens) + (_numInbetweens ? 1 : 0));
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
|
||||
|
||||
g_nancy->_sound->playSound(_spinSound);
|
||||
_animState = kSpin;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_cwHotspot).contains(input.mousePos)) {
|
||||
if (!_enableWraparound && _current == (_dialSrcs.size() / (1 + _numInbetweens) - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveRight : CursorManager::kRotateCW);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
|
||||
_animState != kReset && _animState != kResetAnim) {
|
||||
drawDialFrame(_current * (1 + _numInbetweens) + 1);
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
|
||||
|
||||
if (_current == (_dialSrcs.size() / (1 + _numInbetweens)) - 1) {
|
||||
_current = 0;
|
||||
} else {
|
||||
++_current;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_spinSound);
|
||||
_animState = kSpin;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_selectSound) || _animState == kReset || _animState == kResetAnim || _nextAnim > g_nancy->getTotalPlayTime()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_arrowDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_selectSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_selectSound);
|
||||
pushSequence(_current);
|
||||
_drawSurface.blitFrom(_image1, _arrowSrc, _arrowDest);
|
||||
_animState = kSelect;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 500; // hardcoded
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_resetDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_resetSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image1, _resetSrc, _resetDest);
|
||||
g_nancy->_sound->playSound(_selectSound);
|
||||
_animState = kReset;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 500; // hardcoded
|
||||
_current = 0;
|
||||
_playerSequence.clear();
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::drawDialFrame(uint frame) {
|
||||
if (frame >= _dialSrcs.size() / 2 && !_imageName2.empty()) {
|
||||
_drawSurface.blitFrom(_image2, _dialSrcs[frame], _dialDest);
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image1, _dialSrcs[frame], _dialDest);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::pushSequence(uint id) {
|
||||
if (!_useMoveArrows && id != 0) {
|
||||
// When the puzzle is set to use rotation cursors, the ids in the correct sequence are in reverse order
|
||||
id = (_dialSrcs.size() / (1 + _numInbetweens)) - id;
|
||||
}
|
||||
|
||||
_playerSequence.push_back(id);
|
||||
if (_playerSequence.size() > _correctSequence.size()) {
|
||||
_playerSequence.erase(_playerSequence.begin());
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
103
engines/nancy/action/puzzle/safedialpuzzle.h
Normal file
103
engines/nancy/action/puzzle/safedialpuzzle.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
#define NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles the nancy3 safe puzzle with Chinese characters on the dial,
|
||||
// as well as nancy4's sextant puzzle
|
||||
class SafeDialPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SafeDialPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SafeDialPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum AnimState { kNone, kSpin, kSelect, kReset, kResetAnim };
|
||||
Common::String getRecordTypeName() const override { return "SafeDialPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawDialFrame(uint frame);
|
||||
void pushSequence(uint id);
|
||||
|
||||
Common::Path _imageName1;
|
||||
Common::Path _imageName2;
|
||||
Common::Path _resetImageName;
|
||||
|
||||
bool _enableWraparound = true;
|
||||
|
||||
Common::Rect _dialDest;
|
||||
|
||||
Common::Array<Common::Rect> _dialSrcs;
|
||||
|
||||
Common::Rect _resetDest;
|
||||
Common::Rect _resetSrc;
|
||||
Common::Rect _arrowDest;
|
||||
Common::Rect _arrowSrc;
|
||||
|
||||
Common::Array<Common::Rect> _resetDialSrcs;
|
||||
|
||||
uint16 _resetTurns = 0;
|
||||
|
||||
Common::Array<uint16> _correctSequence;
|
||||
|
||||
Common::Rect _ccwHotspot;
|
||||
Common::Rect _cwHotspot;
|
||||
|
||||
bool _useMoveArrows = false;
|
||||
|
||||
SoundDescription _spinSound;
|
||||
SoundDescription _selectSound;
|
||||
SoundDescription _resetSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image1, _image2, _resetImage;
|
||||
|
||||
uint _numInbetweens = 1;
|
||||
|
||||
Common::Array<uint16> _playerSequence;
|
||||
bool _solved = false;
|
||||
AnimState _animState = kNone;
|
||||
uint32 _nextAnim = 0;
|
||||
uint16 _current = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
301
engines/nancy/action/puzzle/setplayerclock.cpp
Normal file
301
engines/nancy/action/puzzle/setplayerclock.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/* 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/action/puzzle/setplayerclock.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/clock.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
SetPlayerClock::~SetPlayerClock() {
|
||||
Nancy::UI::Clock *clock = NancySceneState.getClock();
|
||||
if (clock) {
|
||||
clock->lockClock(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void SetPlayerClock::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _minutesDest);
|
||||
readRect(stream, _hoursDest);
|
||||
readRect(stream, _AMPMDest);
|
||||
readRect(stream, _timeButtonDest);
|
||||
readRect(stream, _alarmButtonDest);
|
||||
readRect(stream, _setButtonDest);
|
||||
readRect(stream, _cancelButtonDest);
|
||||
readRect(stream, _upButtonDest);
|
||||
readRect(stream, _downButtonDest);
|
||||
readRect(stream, _modeLightDest);
|
||||
|
||||
readRectArray(stream, _minutesSrc, 4);
|
||||
readRectArray(stream, _hoursSrc, 12);
|
||||
|
||||
readRect(stream, _AMSrc);
|
||||
readRect(stream, _PMSrc);
|
||||
readRect(stream, _timeButtonSrc);
|
||||
readRect(stream, _alarmButtonSrc);
|
||||
readRect(stream, _setButtonSrc);
|
||||
readRect(stream, _cancelButtonSrc);
|
||||
readRect(stream, _upButtonSrc);
|
||||
readRect(stream, _downButtonSrc);
|
||||
readRect(stream, _timeLightSrc);
|
||||
readRect(stream, _alarmLightSrc);
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_alarmSetScene.readData(stream);
|
||||
_alarmSoundDelay = stream.readUint16LE();
|
||||
_alarmSetSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
}
|
||||
|
||||
void SetPlayerClock::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
|
||||
_alarmHours = NancySceneState.getPlayerTime().getHours();
|
||||
|
||||
Nancy::UI::Clock *clock = NancySceneState.getClock();
|
||||
if (clock) {
|
||||
clock->lockClock(true);
|
||||
}
|
||||
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_alarmState == kTimeMode) {
|
||||
Time currentTime = NancySceneState.getPlayerTime();
|
||||
int8 hours = currentTime.getHours();
|
||||
int8 minutes = currentTime.getMinutes();
|
||||
|
||||
if (_clearButton && !g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
_drawSurface.fillRect(_timeButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_modeLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _timeLightSrc, _modeLightDest);
|
||||
_clearButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_lastDrawnHours != hours || _lastDrawnMinutes / 15 != minutes / 15) {
|
||||
drawTime(currentTime.getHours(), currentTime.getMinutes());
|
||||
_lastDrawnHours = hours;
|
||||
_lastDrawnMinutes = minutes;
|
||||
}
|
||||
} else {
|
||||
if (_clearButton && !g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
_drawSurface.fillRect(_alarmButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_upButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_downButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_modeLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _alarmLightSrc, _modeLightDest);
|
||||
drawTime(_alarmHours, 0);
|
||||
_clearButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_clearButton) {
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
_drawSurface.fillRect(_setButtonDest, _drawSurface.getTransparentColor());
|
||||
_clearButton = false;
|
||||
}
|
||||
|
||||
if (_alarmState == kWait) {
|
||||
if (_sceneChangeTime != 0) {
|
||||
// Alarm has been set, wait for timer
|
||||
if (g_system->getMillis() > _sceneChangeTime) {
|
||||
_sceneChangeTime = 0;
|
||||
g_nancy->_sound->loadSound(_alarmSetSound);
|
||||
g_nancy->_sound->playSound(_alarmSetSound);
|
||||
}
|
||||
}
|
||||
if (_sceneChangeTime == 0) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_alarmSetSound)) {
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
g_nancy->_sound->stopSound(_alarmSetSound);
|
||||
NancySceneState.setPlayerTime(_alarmHours * 3600000, false);
|
||||
_alarmSetScene.execute();
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Cancel button pressed, go to exit scene
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
_exitScene.execute();
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::handleInput(NancyInput &input) {
|
||||
if (_alarmState == kWait) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel button is active in both time and alarm mode
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_cancelButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Cancel button pressed
|
||||
_drawSurface.blitFrom(_image, _cancelButtonSrc, _cancelButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_alarmState == kTimeMode) {
|
||||
// Alarm button is active only in time mode
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_alarmButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Alarm button pressed
|
||||
_drawSurface.blitFrom(_image, _alarmButtonSrc, _alarmButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_lastDrawnHours = _lastDrawnMinutes = -1;
|
||||
|
||||
_alarmState = kAlarmMode;
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_timeButtonDest).contains(input.mousePos)) {
|
||||
// Time button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Alarm button pressed
|
||||
_drawSurface.blitFrom(_image, _timeButtonSrc, _timeButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_alarmState = kTimeMode;
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_upButtonDest).contains(input.mousePos)) {
|
||||
// Up button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Up button pressed
|
||||
_drawSurface.blitFrom(_image, _upButtonSrc, _upButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_alarmHours = _alarmHours + 1 > 23 ? 0 : _alarmHours + 1;
|
||||
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_downButtonDest).contains(input.mousePos)) {
|
||||
// Down button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Down button pressed
|
||||
_drawSurface.blitFrom(_image, _downButtonSrc, _downButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_alarmHours = _alarmHours - 1 < 0 ? 23 : _alarmHours - 1;
|
||||
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_setButtonDest).contains(input.mousePos)) {
|
||||
// Set button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Down button pressed
|
||||
_drawSurface.blitFrom(_image, _setButtonSrc, _setButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_clearButton = true;
|
||||
_state = kActionTrigger;
|
||||
_alarmState = kWait;
|
||||
_sceneChangeTime = g_system->getMillis() + (_alarmSoundDelay * 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::drawTime(uint16 hours, uint16 minutes) {
|
||||
_drawSurface.fillRect(_hoursDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_minutesDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_AMPMDest, _drawSurface.getTransparentColor());
|
||||
|
||||
_drawSurface.blitFrom(_image, _hoursSrc[(hours - 1 < 0 ? 11 : hours - 1) % 12], _hoursDest);
|
||||
_drawSurface.blitFrom(_image, _minutesSrc[minutes / 15], _minutesDest);
|
||||
_drawSurface.blitFrom(_image, hours / 12 == 0 ? _AMSrc : _PMSrc, _AMPMDest);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
95
engines/nancy/action/puzzle/setplayerclock.h
Normal file
95
engines/nancy/action/puzzle/setplayerclock.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
#define NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Action record implementing an alarm clock. First used in nancy3
|
||||
class SetPlayerClock : public RenderActionRecord {
|
||||
public:
|
||||
enum AlarmState { kTimeMode, kAlarmMode, kWait };
|
||||
SetPlayerClock() : RenderActionRecord(7) {}
|
||||
virtual ~SetPlayerClock();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SetPlayerClock"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawTime(uint16 hours, uint16 minutes);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _minutesDest;
|
||||
Common::Rect _hoursDest;
|
||||
Common::Rect _AMPMDest;
|
||||
Common::Rect _timeButtonDest;
|
||||
Common::Rect _alarmButtonDest;
|
||||
Common::Rect _setButtonDest;
|
||||
Common::Rect _cancelButtonDest;
|
||||
Common::Rect _upButtonDest;
|
||||
Common::Rect _downButtonDest;
|
||||
Common::Rect _modeLightDest;
|
||||
|
||||
Common::Array<Common::Rect> _minutesSrc;
|
||||
Common::Array<Common::Rect> _hoursSrc;
|
||||
Common::Rect _AMSrc;
|
||||
Common::Rect _PMSrc;
|
||||
Common::Rect _timeButtonSrc;
|
||||
Common::Rect _alarmButtonSrc;
|
||||
Common::Rect _setButtonSrc;
|
||||
Common::Rect _cancelButtonSrc;
|
||||
Common::Rect _upButtonSrc;
|
||||
Common::Rect _downButtonSrc;
|
||||
Common::Rect _timeLightSrc;
|
||||
Common::Rect _alarmLightSrc;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SceneChangeWithFlag _alarmSetScene;
|
||||
uint16 _alarmSoundDelay = 0;
|
||||
SoundDescription _alarmSetSound; // NO SOUND in MHM
|
||||
SceneChangeWithFlag _exitScene;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int8 _lastDrawnHours = -1;
|
||||
int8 _lastDrawnMinutes = -1;
|
||||
int8 _alarmHours = -1;
|
||||
bool _clearButton = true;
|
||||
Time _sceneChangeTime;
|
||||
|
||||
AlarmState _alarmState = kTimeMode;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
299
engines/nancy/action/puzzle/sliderpuzzle.cpp
Normal file
299
engines/nancy/action/puzzle/sliderpuzzle.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/sliderpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SliderPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void SliderPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (SliderPuzzleData *)NancySceneState.getPuzzleData(SliderPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_width = stream.readUint16LE();
|
||||
_height = stream.readUint16LE();
|
||||
|
||||
_srcRects.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
readRectArray(stream, _srcRects[y], _width, 6);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 16);
|
||||
|
||||
_destRects.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
readRectArray(stream, _destRects[y], _width, 6);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 16);
|
||||
|
||||
_screenPosition = _destRects[0][0];
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_screenPosition.extend(_destRects[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
_retainState = stream.readByte();
|
||||
|
||||
_startTileOrder.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
_startTileOrder[y].resize(_width);
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_startTileOrder[y][x] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((6 - _width) * 2);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 2);
|
||||
} else {
|
||||
auto *spuzData = GetEngineData(SPUZ);
|
||||
assert(spuzData);
|
||||
_startTileOrder = spuzData->tileOrder[NancySceneState.getDifficulty()];
|
||||
}
|
||||
|
||||
_correctTileOrder.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
_correctTileOrder[y].resize(_width);
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_correctTileOrder[y][x] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((6 - _width) * 2);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 2);
|
||||
|
||||
_clickSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SliderPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (SliderPuzzleData *)NancySceneState.getPuzzleData(SliderPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
if (!_puzzleState->playerHasTriedPuzzle || !_retainState) {
|
||||
_puzzleState->playerTileOrder = _startTileOrder;
|
||||
_puzzleState->playerHasTriedPuzzle = true;
|
||||
}
|
||||
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
drawTile(_puzzleState->playerTileOrder[y][x], x, y);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
g_nancy->_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 (_puzzleState->playerTileOrder[y][x] != _correctTileOrder[y][x]) {
|
||||
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:
|
||||
_solveExitScene.execute();
|
||||
_puzzleState->playerHasTriedPuzzle = false;
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_clickSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void SliderPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
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 && _puzzleState->playerTileOrder[y][x - 1] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kLeft;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if ((int)x < _width - 1 && _puzzleState->playerTileOrder[y][x + 1] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kRight;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if (y > 0 && _puzzleState->playerTileOrder[y - 1][x] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kUp;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if ((int)y < _height - 1 && _puzzleState->playerTileOrder[y + 1][x] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kDown;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBreak) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTileX != -1) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
switch (direction) {
|
||||
case kUp: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX, currentTileY - 1);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY - 1][currentTileX] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kDown: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX, currentTileY + 1);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY + 1][currentTileX] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kLeft: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX - 1, currentTileY);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX - 1] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kRight: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX + 1, currentTileY);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX + 1] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SliderPuzzle::drawTile(int tileID, uint posX, uint posY) {
|
||||
if (tileID < 0) {
|
||||
undrawTile(posX, posY);
|
||||
return;
|
||||
}
|
||||
|
||||
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, g_nancy->_graphics->getTransColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
77
engines/nancy/action/puzzle/sliderpuzzle.h
Normal file
77
engines/nancy/action/puzzle/sliderpuzzle.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SLIDERPUZZLE_H
|
||||
#define NANCY_ACTION_SLIDERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct SPUZ;
|
||||
struct SliderPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class SliderPuzzle: public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
SliderPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SliderPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
SliderPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
Common::Path _imageName;
|
||||
uint16 _width = 0;
|
||||
uint16 _height = 0;
|
||||
Common::Array<Common::Array<Common::Rect>> _srcRects;
|
||||
Common::Array<Common::Array<Common::Rect>> _destRects;
|
||||
Common::Array<Common::Array<int16>> _startTileOrder;
|
||||
Common::Array<Common::Array<int16>> _correctTileOrder;
|
||||
SoundDescription _clickSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
bool _retainState = true;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SliderPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawTile(int tileID, uint posX, uint posY);
|
||||
void undrawTile(uint posX, uint posY);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SLIDERPUZZLE_H
|
||||
311
engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
Normal file
311
engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
/* 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/action/puzzle/soundequalizerpuzzle.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/scrollbar.h"
|
||||
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class ViewportScrollbar : public UI::Scrollbar {
|
||||
public:
|
||||
ViewportScrollbar(uint16 zOrder, const Common::Rect &srcBounds, Graphics::ManagedSurface &srcSurf, const Common::Point &topPosition, uint16 scrollDistance, bool isVertical = true) :
|
||||
Scrollbar(zOrder, srcBounds, srcSurf, topPosition, scrollDistance, isVertical) {}
|
||||
virtual ~ViewportScrollbar() = default;
|
||||
|
||||
bool handleInput(NancyInput &input) {
|
||||
if (_screenPosition.contains(input.mousePos)) {
|
||||
input.input &= (~NancyInput::kRightMouseButtonUp);
|
||||
|
||||
Scrollbar::handleInput(input);
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SoundEqualizerPuzzle::~SoundEqualizerPuzzle() {
|
||||
for (auto *scrollbar : _sliders) {
|
||||
delete scrollbar;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
const VIEW *viewportData = (const VIEW *)g_nancy->getEngineData("VIEW");
|
||||
assert(viewportData);
|
||||
Common::Rect vpPos = viewportData->screenPosition;
|
||||
|
||||
if (_puzzleState->sliderValues[0] == 255) {
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_puzzleState->sliderValues[i] = _sliderInitialPositions[i];
|
||||
}
|
||||
}
|
||||
|
||||
_sliders.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
Common::Point screenDest(_sliderX[i], _sliderYMax[i] - (_sliderSrc.height() / 2));
|
||||
screenDest.x += vpPos.left;
|
||||
screenDest.y += vpPos.top;
|
||||
_sliders[i] = new ViewportScrollbar( 8,
|
||||
_sliderSrc,
|
||||
_image,
|
||||
screenDest,
|
||||
_sliderYMin[i] - _sliderYMax[i]);
|
||||
_sliders[i]->init();
|
||||
_sliders[i]->setPosition((float)(100 - _puzzleState->sliderValues[i]) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::registerGraphics() {
|
||||
for (auto *scrollbar : _sliders) {
|
||||
scrollbar->registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (SoundEqualizerPuzzleData *)NancySceneState.getPuzzleData(SoundEqualizerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
uint16 difficulty = NancySceneState.getDifficulty();
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _buttonSrc);
|
||||
readRect(stream, _buttonDest);
|
||||
readRect(stream, _sliderSrc);
|
||||
|
||||
_sliderX.resize(6);
|
||||
_sliderYMin.resize(6);
|
||||
_sliderYMax.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_sliderX[i] = stream.readUint16LE();
|
||||
_sliderYMin[i] = stream.readUint16LE();
|
||||
_sliderYMax[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _lightSrc);
|
||||
|
||||
_lightDests.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
readRectArray(stream, _lightDests[i], 10);
|
||||
}
|
||||
|
||||
_sliderInitialPositions.resize(6);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
for (uint j = 0; j < 6; ++j) {
|
||||
_sliderInitialPositions[j] = stream.readUint16LE();
|
||||
}
|
||||
} else {
|
||||
stream.skip(12);
|
||||
}
|
||||
}
|
||||
|
||||
_sounds.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_sounds[i].readNormal(stream);
|
||||
}
|
||||
|
||||
_minVolume.resize(3);
|
||||
_maxVolume.resize(3);
|
||||
_minRate.resize(3);
|
||||
_maxRate.resize(3);
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
for (uint j = 0; j < 3; ++j) {
|
||||
_minVolume[j] = stream.readUint16LE();
|
||||
_maxVolume[j] = stream.readUint16LE();
|
||||
_minRate[j] = stream.readUint32LE();
|
||||
_maxRate[j] = stream.readUint32LE();
|
||||
}
|
||||
} else {
|
||||
stream.skip(12 * 3);
|
||||
}
|
||||
}
|
||||
|
||||
_solveChannelID = stream.readUint16LE();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
_solveMinVolume = stream.readUint16LE();
|
||||
_solveMaxVolume = stream.readUint16LE();
|
||||
_solveMinRate = stream.readUint32LE();
|
||||
_solveMaxRate = stream.readUint32LE();
|
||||
} else {
|
||||
stream.skip(12);
|
||||
}
|
||||
}
|
||||
|
||||
_exitScene.readData(stream);
|
||||
stream.skip(2);
|
||||
_exitSound.readNormal(stream);
|
||||
|
||||
_solveFlag.label = stream.readSint16LE();
|
||||
_solveFlag.flag = stream.readByte();
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (SoundEqualizerPuzzleData *)NancySceneState.getPuzzleData(SoundEqualizerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
g_nancy->_sound->loadSound(_sounds[i]);
|
||||
g_nancy->_sound->playSound(_sounds[i]);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
updateSlider(i);
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
break;
|
||||
case kRun:
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_exitSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
g_nancy->_sound->stopSound(_sounds[i]);
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_exitScene);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state == kActionTrigger) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
return;
|
||||
} else if (_state == kBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Exit button pressed
|
||||
_drawSurface.blitFrom(_image, _buttonSrc, _buttonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->loadSound(_exitSound);
|
||||
g_nancy->_sound->playSound(_exitSound);
|
||||
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
if (_sliders[i]->handleInput(input)) {
|
||||
updateSlider(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::updateSlider(uint sliderID) {
|
||||
float sliderVal = 1 - _sliders[sliderID]->getPos();
|
||||
_puzzleState->sliderValues[sliderID] = sliderVal * 100;
|
||||
|
||||
if (sliderID < 3) {
|
||||
// First three sliders change pitch, except when the slider
|
||||
// controls the "solve" sound; in that case it does nothing
|
||||
if (sliderID != _solveChannelID) {
|
||||
g_nancy->_sound->setRate(_sounds[sliderID],
|
||||
_minRate[sliderID] + (_maxRate[sliderID] - _minRate[sliderID]) * sliderVal);
|
||||
}
|
||||
} else {
|
||||
// Other three sliders change volume;
|
||||
// "solve" sound slider behaves as an on/off switch
|
||||
if (sliderID - 3 != _solveChannelID) {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3],
|
||||
_minVolume[sliderID - 3] + (_maxVolume[sliderID - 3] - _minVolume[sliderID - 3]) * sliderVal);
|
||||
} else {
|
||||
if (sliderVal * 100 >= _solveMinVolume && sliderVal * 100 <= _solveMaxVolume) {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3], _maxVolume[sliderID - 3]);
|
||||
|
||||
// Since the rate for the "solve" sound never actually changes,
|
||||
// we only need the volume to be correct.
|
||||
NancySceneState.setEventFlag(_solveFlag);
|
||||
} else {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3], _minVolume[sliderID - 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint numLights = sliderVal * 10;
|
||||
|
||||
if (numLights < 10) {
|
||||
Common::Rect clear = _lightDests[sliderID][numLights];
|
||||
clear.extend(_lightDests[sliderID][9]);
|
||||
|
||||
_drawSurface.fillRect(clear, _drawSurface.getTransparentColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < numLights; ++i) {
|
||||
_drawSurface.blitFrom(_image, _lightSrc, _lightDests[sliderID][i]);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
99
engines/nancy/action/puzzle/soundequalizerpuzzle.h
Normal file
99
engines/nancy/action/puzzle/soundequalizerpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
#define NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace UI {
|
||||
class Scrollbar;
|
||||
}
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct SoundEqualizerPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class ViewportScrollbar;
|
||||
|
||||
class SoundEqualizerPuzzle: public RenderActionRecord {
|
||||
public:
|
||||
SoundEqualizerPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SoundEqualizerPuzzle();
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _buttonSrc;
|
||||
Common::Rect _buttonDest;
|
||||
Common::Rect _sliderSrc;
|
||||
|
||||
Common::Array<uint16> _sliderX;
|
||||
Common::Array<uint16> _sliderYMin;
|
||||
Common::Array<uint16> _sliderYMax;
|
||||
|
||||
Common::Rect _lightSrc;
|
||||
Common::Array<Common::Array<Common::Rect>> _lightDests;
|
||||
|
||||
Common::Array<uint16> _sliderInitialPositions;
|
||||
|
||||
Common::Array<SoundDescription> _sounds;
|
||||
|
||||
Common::Array<uint16> _minVolume;
|
||||
Common::Array<uint16> _maxVolume;
|
||||
Common::Array<uint16> _minRate;
|
||||
Common::Array<uint16> _maxRate;
|
||||
|
||||
uint16 _solveChannelID = 0;
|
||||
uint16 _solveMinVolume = 0;
|
||||
uint16 _solveMaxVolume = 0;
|
||||
uint16 _solveMinRate = 0;
|
||||
uint16 _solveMaxRate = 0;
|
||||
|
||||
SceneChangeDescription _exitScene;
|
||||
SoundDescription _exitSound;
|
||||
|
||||
FlagDescription _solveFlag;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<ViewportScrollbar *> _sliders;
|
||||
|
||||
SoundEqualizerPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SoundEqualizerPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void updateSlider(uint sliderID);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/soundmatchpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/soundmatchpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/soundmatchpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SoundMatchPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Whale sounds puzzle");
|
||||
NancySceneState.setEventFlag(436, g_nancy->_true); // EV_Solved_Whale_Call
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 2936;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/soundmatchpuzzle.h
Normal file
51
engines/nancy/action/puzzle/soundmatchpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
#define NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Whale sound matching puzzle in Nancy 9
|
||||
|
||||
class SoundMatchPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SoundMatchPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SoundMatchPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SoundMatchPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
301
engines/nancy/action/puzzle/spigotpuzzle.cpp
Normal file
301
engines/nancy/action/puzzle/spigotpuzzle.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/spigotpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SpigotPuzzle::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);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void SpigotPuzzle::updateGraphics() {
|
||||
if (_pushedButtonID != -1) {
|
||||
if (g_nancy->getTotalPlayTime() >= _nextAnimTime) {
|
||||
_animatingLetterID = _pushedButtonID;
|
||||
_drawSurface.fillRect(_buttonDests[_pushedButtonID], _drawSurface.getTransparentColor());
|
||||
_pushedButtonID = -1;
|
||||
_nextAnimTime = 0;
|
||||
|
||||
uint numSpins = _numSpins[_numPulls[_animatingLetterID] - 1];
|
||||
_frameID = numSpins * _numInbetweens;
|
||||
}
|
||||
}
|
||||
|
||||
if (_animatingSpigotID != -1) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
if (curTime >= _nextAnimTime) {
|
||||
if (_nextAnimTime == 0) {
|
||||
_nextAnimTime = curTime + 100;
|
||||
} else {
|
||||
_nextAnimTime += 100;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frameID != _spigotAnimSrcs[_animatingSpigotID].size()) {
|
||||
_drawSurface.blitFrom(_image, _spigotAnimSrcs[_animatingSpigotID][_frameID], _spigotDests[_animatingSpigotID]);
|
||||
_needsRedraw = true;
|
||||
|
||||
++_frameID;
|
||||
} else {
|
||||
// Increment the number and end the animation
|
||||
_numPulls[_animatingSpigotID] = MIN<uint16>(_numPulls[_animatingSpigotID] + 1, 6);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[_animatingSpigotID][_numPulls[_animatingSpigotID]], _digitDests[_animatingSpigotID]);
|
||||
|
||||
// Also, clear the last drawn spigot frame
|
||||
_drawSurface.fillRect(_spigotDests[_animatingSpigotID], _drawSurface.getTransparentColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
_animatingSpigotID = -1;
|
||||
_frameID = 0;
|
||||
_nextAnimTime = 0;
|
||||
}
|
||||
} else if (_animatingLetterID != -1) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
if (curTime >= _nextAnimTime) {
|
||||
if (_nextAnimTime == 0) {
|
||||
_nextAnimTime = curTime + _letterTime * 200;
|
||||
} else {
|
||||
_nextAnimTime += _letterTime * 200;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frameID != 0) {
|
||||
if (++_currentAnimOrder[_animatingLetterID] >= _numLetters * _numInbetweens) {
|
||||
_currentAnimOrder[_animatingLetterID] = 0;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_letterSound);
|
||||
_drawSurface.blitFrom(_image, _letterSrcs[_animatingLetterID][_currentAnimOrder[_animatingLetterID]], _letterDests[_animatingLetterID]);
|
||||
_needsRedraw = true;
|
||||
|
||||
--_frameID;
|
||||
} else {
|
||||
// Clear the number
|
||||
_numPulls[_animatingLetterID] = 0;
|
||||
_drawSurface.fillRect(_digitDests[_animatingLetterID], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
// Set the order at the end of the animation to avoid "solving" while animating
|
||||
_currentOrder[_animatingLetterID] = _currentAnimOrder[_animatingLetterID] / _numInbetweens;
|
||||
_animatingLetterID = -1;
|
||||
_nextAnimTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpigotPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numSpigots = stream.readUint16LE();
|
||||
_numLetters = stream.readUint16LE();
|
||||
_numInbetweens = stream.readUint16LE();
|
||||
|
||||
_startOrder.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_startOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 2);
|
||||
|
||||
_numSpins.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_numSpins[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRectArray(stream, _spigotDests, _numSpigots, 6);
|
||||
readRectArray(stream, _spigotHotspots, _numSpigots, 6);
|
||||
readRectArray(stream, _letterDests, _numSpigots, 6);
|
||||
readRectArray(stream, _digitDests, _numSpigots, 6);
|
||||
readRectArray(stream, _buttonDests, _numSpigots, 6);
|
||||
|
||||
uint16 numSpigotAnimationFrames = stream.readUint16LE();
|
||||
_spigotAnimSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_spigotAnimSrcs[i].resize(numSpigotAnimationFrames);
|
||||
|
||||
uint32 x = stream.readUint32LE();
|
||||
uint32 y = stream.readUint32LE();
|
||||
uint16 deltaX = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE() + 1;
|
||||
|
||||
for (uint j = 0; j < numSpigotAnimationFrames; ++j) {
|
||||
_spigotAnimSrcs[i][j] = Common::Rect(x + j * deltaX, y, x + j * deltaX + deltaX, y + height);
|
||||
}
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 12);
|
||||
|
||||
_digitSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
readRectArray(stream, _digitSrcs[i], 7);
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 7 * 16);
|
||||
|
||||
readRectArray(stream, _buttonSrcs, _numSpigots, 6);
|
||||
|
||||
_letterSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
readRectArray(stream, _letterSrcs[i], _numLetters * _numInbetweens, 7 * 2);
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 7 * 2 * 16);
|
||||
|
||||
_letterTime = stream.readUint16LE();
|
||||
|
||||
_correctOrder.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_correctOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 2);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_letterSound.readNormal(stream);
|
||||
_spigotSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SpigotPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
g_nancy->_sound->loadSound(_letterSound);
|
||||
g_nancy->_sound->loadSound(_spigotSound);
|
||||
|
||||
_currentOrder = _startOrder;
|
||||
_currentAnimOrder.resize(_currentOrder.size());
|
||||
for (uint i = 0; i < _currentAnimOrder.size(); ++i) {
|
||||
_currentAnimOrder[i] = _currentOrder[i] * _numInbetweens;
|
||||
}
|
||||
_numPulls.resize(_numSpigots, 0);
|
||||
|
||||
// Draw the start letters, in case the background ones are different
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_drawSurface.blitFrom(_image, _letterSrcs[i][_currentAnimOrder[i]], _letterDests[i]);
|
||||
}
|
||||
_needsRedraw = true;
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_currentOrder == _correctOrder) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_solved) {
|
||||
// Sound delay not used
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpigotPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _animatingSpigotID != -1 || _animatingLetterID != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect vpScreenPos = NancySceneState.getViewport().convertViewportToScreen(_screenPosition);
|
||||
if (!vpScreenPos.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point mousePos = input.mousePos;
|
||||
mousePos -= { vpScreenPos.left, vpScreenPos.top };
|
||||
|
||||
if (_exitHotspot.contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
if (_spigotHotspots[i].contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_spigotSound);
|
||||
_animatingSpigotID = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_numPulls[i] && _buttonDests[i].contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
_pushedButtonID = i;
|
||||
_nextAnimTime = g_nancy->getTotalPlayTime() + 250;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
99
engines/nancy/action/puzzle/spigotpuzzle.h
Normal file
99
engines/nancy/action/puzzle/spigotpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
#define NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle in nancy7 where you pull spigots to input a password
|
||||
class SpigotPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SpigotPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SpigotPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SpigotPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint _numSpigots = 0;
|
||||
uint _numLetters = 0;
|
||||
uint _numInbetweens = 0;
|
||||
|
||||
Common::Array<uint16> _startOrder;
|
||||
Common::Array<uint16> _numSpins;
|
||||
|
||||
Common::Array<Common::Rect> _spigotDests;
|
||||
Common::Array<Common::Rect> _spigotHotspots;
|
||||
Common::Array<Common::Rect> _letterDests;
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
|
||||
Common::Array<Common::Array<Common::Rect>> _spigotAnimSrcs;
|
||||
Common::Array<Common::Array<Common::Rect>> _digitSrcs;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Array<Common::Rect>> _letterSrcs;
|
||||
|
||||
uint16 _letterTime = 0; // 1 unit = 200ms
|
||||
Common::Array<uint16> _correctOrder;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SoundDescription _letterSound;
|
||||
SoundDescription _spigotSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
Common::Array<uint16> _currentOrder;
|
||||
Common::Array<uint16> _currentAnimOrder;
|
||||
Common::Array<uint16> _numPulls;
|
||||
|
||||
int _animatingSpigotID = -1;
|
||||
int _animatingLetterID = -1;
|
||||
int _pushedButtonID = -1;
|
||||
uint _frameID = 0;
|
||||
uint32 _nextAnimTime = 0;
|
||||
|
||||
bool _solved = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
453
engines/nancy/action/puzzle/tangrampuzzle.cpp
Normal file
453
engines/nancy/action/puzzle/tangrampuzzle.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/tangrampuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
TangramPuzzle::~TangramPuzzle() {
|
||||
delete[] _zBuffer;
|
||||
}
|
||||
|
||||
void TangramPuzzle::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(_tileImageName, _tileImage);
|
||||
g_nancy->_resource->loadImage(_maskImageName, _maskImage);
|
||||
|
||||
_zBuffer = new byte[_drawSurface.w * _drawSurface.h];
|
||||
memset(_zBuffer, -1, _drawSurface.w * _drawSurface.h);
|
||||
|
||||
_tiles.resize(_tileSrcs.size() + 1);
|
||||
|
||||
// First, add the mask as its own tile for easier handling
|
||||
Tile *curTile = &_tiles[0];
|
||||
curTile->_srcImage.create(_maskImage, _maskImage.getBounds());
|
||||
curTile->_drawSurface.copyFrom(_tiles[0]._srcImage);
|
||||
curTile->_id = 0;
|
||||
curTile->drawMask();
|
||||
curTile->moveTo(_maskImage.getBounds());
|
||||
curTile->setTransparent(true);
|
||||
curTile->setVisible(true);
|
||||
drawToBuffer(*curTile);
|
||||
curTile->setZ(_z + 1);
|
||||
|
||||
// Then, add the actual tiles
|
||||
for (uint i = 0; i < _tileSrcs.size(); ++i) {
|
||||
curTile = &_tiles[i + 1];
|
||||
curTile->_srcImage.create(_tileImage, _tileSrcs[i]);
|
||||
curTile->_drawSurface.copyFrom(curTile->_srcImage);
|
||||
curTile->_id = i + 1;
|
||||
curTile->moveTo(_tileDests[i]);
|
||||
curTile->setTransparent(true);
|
||||
curTile->setVisible(true);
|
||||
curTile->setZ(_z + curTile->_id + 1);
|
||||
curTile->drawMask();
|
||||
drawToBuffer(*curTile);
|
||||
|
||||
// Draw the highlighted tile
|
||||
curTile->_highlightedSrcImage.copyFrom(curTile->_srcImage);
|
||||
|
||||
Graphics::PixelFormat format = curTile->_highlightedSrcImage.format;
|
||||
for (int y = 0; y < curTile->_highlightedSrcImage.h; ++y) {
|
||||
uint16 *p = (uint16 *)curTile->_highlightedSrcImage.getBasePtr(0, y);
|
||||
for (int x = 0; x < curTile->_highlightedSrcImage.w; ++x) {
|
||||
if (*p != g_nancy->_graphics->getTransColor()) {
|
||||
// I'm not sure *3/2 is the exact formula but it's close enough
|
||||
byte r, g, b;
|
||||
format.colorToRGB(*p, r, g, b);
|
||||
r = (byte)((((uint16)r) * 3) >> 1);
|
||||
g = (byte)((((uint16)g) * 3) >> 1);
|
||||
b = (byte)((((uint16)b) * 3) >> 1);
|
||||
*p = (uint16)format.RGBToColor((byte)r, (byte)g, (byte)b);
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::registerGraphics() {
|
||||
for (Tile &tile : _tiles) {
|
||||
tile.registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _tileImageName);
|
||||
readFilename(stream, _maskImageName);
|
||||
|
||||
stream.skip(2); // Supposedly number of tiles, actually useless
|
||||
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
Common::Rect src, dest;
|
||||
readRect(stream, src);
|
||||
readRect(stream, dest);
|
||||
|
||||
if ((src.width() == 1 && src.height() == 1) || (dest.width() == 1 && dest.height() == 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_tileSrcs.push_back(src);
|
||||
_tileDests.push_back(dest);
|
||||
}
|
||||
|
||||
readRect(stream, _maskSolveBounds);
|
||||
|
||||
_pickUpSound.readNormal(stream);
|
||||
_putDownSound.readNormal(stream);
|
||||
_rotateSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TangramPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_putDownSound);
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_pickedUpTile == -1 && _shouldCheck) {
|
||||
for (int y = 0; y < _maskSolveBounds.height(); ++y) {
|
||||
byte *p = &_zBuffer[(y + _maskSolveBounds.top) * _drawSurface.w + _maskSolveBounds.left];
|
||||
for (int x = 0; x < _maskSolveBounds.width(); ++x) {
|
||||
if (*p == 0) {
|
||||
_shouldCheck = false;
|
||||
return;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
break;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_pickUpSound);
|
||||
g_nancy->_sound->stopSound(_putDownSound);
|
||||
g_nancy->_sound->stopSound(_rotateSound);
|
||||
|
||||
finishExecution();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect viewport = NancySceneState.getViewport().getScreenPosition();
|
||||
|
||||
if (!viewport.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point mousePos = input.mousePos;
|
||||
mousePos.x -= viewport.left;
|
||||
mousePos.y -= viewport.top;
|
||||
|
||||
viewport.moveTo(Common::Point(0, 0));
|
||||
|
||||
if (_pickedUpTile == -1) {
|
||||
// Not holding a tile, check what's under the cursor
|
||||
byte idUnderMouse = _zBuffer[mousePos.y * _drawSurface.w + mousePos.x];
|
||||
|
||||
if (idUnderMouse != 0 && idUnderMouse != (byte)-1) {
|
||||
// A tile is under the cursor
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
pickUpTile(idUnderMouse);
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
} else if (input.input & NancyInput::kRightMouseButtonUp) {
|
||||
rotateTile(idUnderMouse);
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// No tile under cursor, check exit hotspot
|
||||
if (_exitHotspot.contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Currently holding a tile
|
||||
Tile &tileHolding = _tiles[_pickedUpTile];
|
||||
|
||||
// Check if we need to place it back down
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
putDownTile(_pickedUpTile);
|
||||
g_nancy->_sound->playSound(_putDownSound);
|
||||
return;
|
||||
}
|
||||
|
||||
tileHolding.handleInput(input);
|
||||
bool rotated = false;
|
||||
|
||||
// Check if we need to rotate it
|
||||
if (input.input & NancyInput::kRightMouseButtonUp) {
|
||||
rotateTile(_pickedUpTile);
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotated = true;
|
||||
}
|
||||
|
||||
if (!rotated) {
|
||||
// Check if we need to highlight, but only if we haven't rotated,
|
||||
// since rotateTile() already checks as well
|
||||
if (checkBuffer(tileHolding) != tileHolding._isHighlighted) {
|
||||
tileHolding.setHighlighted(!tileHolding._isHighlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::drawToBuffer(const Tile &tile, Common::Rect subRect) {
|
||||
if (subRect.isEmpty()) {
|
||||
subRect = tile._screenPosition;
|
||||
}
|
||||
|
||||
uint16 xDiff = subRect.left - tile._screenPosition.left;
|
||||
uint16 yDiff = subRect.top - tile._screenPosition.top;
|
||||
|
||||
for (int y = 0; y < subRect.height(); ++y) {
|
||||
byte *src = &tile._mask[(y + yDiff) * tile._drawSurface.w + xDiff];
|
||||
byte *dest = &_zBuffer[(subRect.top + y) * _drawSurface.w + subRect.left];
|
||||
for (int x = 0; x < subRect.width(); ++x) {
|
||||
if (*src != (byte)-1) {
|
||||
*dest = *src;
|
||||
}
|
||||
|
||||
++src;
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::pickUpTile(uint id) {
|
||||
assert(id < _tiles.size() && id != 0);
|
||||
|
||||
Tile &tileToPickUp = _tiles[id];
|
||||
|
||||
moveToTop(id);
|
||||
_pickedUpTile = id;
|
||||
redrawBuffer(tileToPickUp._screenPosition);
|
||||
tileToPickUp.pickUp();
|
||||
|
||||
// Make sure we don't have a frame with the correct zOrder, but wrong position
|
||||
NancyInput input = g_nancy->_input->getInput();
|
||||
input.input = 0;
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
void TangramPuzzle::putDownTile(uint id) {
|
||||
Tile &tile = _tiles[id];
|
||||
_pickedUpTile = -1;
|
||||
|
||||
drawToBuffer(tile);
|
||||
tile.putDown();
|
||||
|
||||
if (tile._isHighlighted) {
|
||||
tile.setHighlighted(false);
|
||||
}
|
||||
|
||||
_shouldCheck = true;
|
||||
}
|
||||
|
||||
void TangramPuzzle::rotateTile(uint id) {
|
||||
assert(id < _tiles.size() && id != 0);
|
||||
|
||||
Tile &tileToRotate = _tiles[id];
|
||||
|
||||
if (tileToRotate._rotation == 3) {
|
||||
tileToRotate._rotation = 0;
|
||||
} else {
|
||||
++tileToRotate._rotation;
|
||||
}
|
||||
|
||||
moveToTop(id);
|
||||
|
||||
Common::Rect oldPos = tileToRotate._screenPosition;
|
||||
|
||||
if (_pickedUpTile != -1 && checkBuffer(tileToRotate)) {
|
||||
tileToRotate.setHighlighted(true);
|
||||
} else {
|
||||
tileToRotate.setHighlighted(false);
|
||||
}
|
||||
|
||||
Common::Rect newPos = tileToRotate._drawSurface.getBounds();
|
||||
newPos.moveTo(oldPos.left + oldPos.width() / 2 - newPos.width() / 2, oldPos.top + oldPos.height() / 2 - newPos.height() / 2);
|
||||
tileToRotate.moveTo(newPos);
|
||||
_needsRedraw = true;
|
||||
|
||||
tileToRotate.drawMask();
|
||||
tileToRotate._needsRedraw = true;
|
||||
|
||||
if (_pickedUpTile == -1) {
|
||||
redrawBuffer(oldPos);
|
||||
drawToBuffer(tileToRotate);
|
||||
_shouldCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::moveToTop(uint id) {
|
||||
for (uint i = 1; i < _tiles.size(); ++i) {
|
||||
Tile &tile = _tiles[i];
|
||||
if (tile._z > _tiles[id]._z) {
|
||||
tile.setZ(tile._z - 1);
|
||||
tile.registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
_tiles[id].setZ(_z + _tiles.size());
|
||||
_tiles[id].registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::redrawBuffer(const Common::Rect &rect) {
|
||||
// Redraw the zBuffer for all intersecting pixels, except for the topmost tile
|
||||
for (int y = 0; y < rect.height(); ++y) {
|
||||
byte *dest = &_zBuffer[(y + rect.top) * _drawSurface.w + rect.left];
|
||||
memset(dest, -1, rect.width());
|
||||
}
|
||||
|
||||
for (uint z = _z + 1; z < _z + _tiles.size(); ++z) {
|
||||
for (uint i = 0; i < _tiles.size() - 1; ++i) {
|
||||
Tile &tile = _tiles[i];
|
||||
if (tile._z == z) {
|
||||
if (tile._screenPosition.intersects(rect)) {
|
||||
drawToBuffer(tile, tile._screenPosition.findIntersectingRect(rect));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TangramPuzzle::checkBuffer(const Tile &tile) const {
|
||||
// Check if the provided tile has any pixel overlapping with a non-zero in the zBuffer
|
||||
// In other words, this checks if we're placing on a valid empty spot
|
||||
for (int y = 0; y < tile._drawSurface.h; ++y) {
|
||||
const byte *tilePtr = &tile._mask[y * tile._drawSurface.w];
|
||||
const byte *bufPtr = &_zBuffer[(y + tile._screenPosition.top) * _drawSurface.w + tile._screenPosition.left];
|
||||
for (int x = 0; x < tile._drawSurface.w; ++x) {
|
||||
if (*tilePtr != (byte)-1 && *bufPtr != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++tilePtr;
|
||||
++bufPtr;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TangramPuzzle::Tile::Tile() : _mask(nullptr), _id(0), _rotation(0), _isHighlighted(false) {}
|
||||
|
||||
TangramPuzzle::Tile::~Tile() {
|
||||
delete[] _mask;
|
||||
}
|
||||
|
||||
void TangramPuzzle::Tile::drawMask() {
|
||||
if (!_mask) {
|
||||
_mask = new byte[_drawSurface.w * _drawSurface.h];
|
||||
}
|
||||
|
||||
uint16 transColor = g_nancy->_graphics->getTransColor();
|
||||
for (int y = 0; y < _drawSurface.h; ++y) {
|
||||
uint16 *src = (uint16 *)_drawSurface.getBasePtr(0, y);
|
||||
for (int x = 0; x < _drawSurface.w; ++x) {
|
||||
if (*src == transColor) {
|
||||
_mask[y * _drawSurface.w + x] = (byte)-1;
|
||||
} else {
|
||||
_mask[y * _drawSurface.w + x] = _id;
|
||||
}
|
||||
++src;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::Tile::setHighlighted(bool highlighted) {
|
||||
_isHighlighted = highlighted;
|
||||
GraphicsManager::rotateBlit(_isHighlighted ? _highlightedSrcImage : _srcImage,
|
||||
_drawSurface,
|
||||
_rotation);
|
||||
setTransparent(true);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
115
engines/nancy/action/puzzle/tangrampuzzle.h
Normal file
115
engines/nancy/action/puzzle/tangrampuzzle.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
#define NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles a specific type of puzzle where clicking an object rotates it,
|
||||
// as well as several other objects linked to it. Examples are the sun/moon
|
||||
// and staircase spindle puzzles in nancy3
|
||||
class TangramPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
TangramPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TangramPuzzle();
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TangramPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
class Tile : public Misc::MouseFollowObject {
|
||||
friend class TangramPuzzle;
|
||||
public:
|
||||
Tile();
|
||||
virtual ~Tile();
|
||||
|
||||
void drawMask();
|
||||
void setHighlighted(bool highlighted);
|
||||
|
||||
Graphics::ManagedSurface _srcImage;
|
||||
Graphics::ManagedSurface _highlightedSrcImage;
|
||||
byte *_mask;
|
||||
byte _id;
|
||||
byte _rotation;
|
||||
bool _isHighlighted;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
void drawToBuffer(const Tile &tile, Common::Rect subRect = Common::Rect());
|
||||
|
||||
void pickUpTile(uint id);
|
||||
void putDownTile(uint id);
|
||||
void rotateTile(uint id);
|
||||
|
||||
void moveToTop(uint id);
|
||||
void redrawBuffer(const Common::Rect &rect);
|
||||
|
||||
bool checkBuffer(const Tile &tile) const;
|
||||
|
||||
Common::Path _tileImageName;
|
||||
Common::Path _maskImageName;
|
||||
|
||||
Common::Array<Common::Rect> _tileSrcs;
|
||||
Common::Array<Common::Rect> _tileDests;
|
||||
|
||||
Common::Rect _maskSolveBounds;
|
||||
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _putDownSound;
|
||||
SoundDescription _rotateSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _tileImage;
|
||||
Graphics::ManagedSurface _maskImage;
|
||||
byte *_zBuffer = nullptr;
|
||||
|
||||
Common::Array<Tile> _tiles;
|
||||
|
||||
int16 _pickedUpTile = -1;
|
||||
bool _shouldCheck = false;
|
||||
bool _solved = false;
|
||||
|
||||
uint _pixelAdjustment = 5;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
578
engines/nancy/action/puzzle/telephone.cpp
Normal file
578
engines/nancy/action/puzzle/telephone.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/font.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/telephone.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void Telephone::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);
|
||||
g_nancy->_resource->loadImage(_displayAnimName, _animImage);
|
||||
|
||||
if (_isNewPhone) {
|
||||
_font = g_nancy->_graphics->getFont(_displayFont);
|
||||
}
|
||||
|
||||
// Set the phone tutorial flag to false for Nancy9, so that
|
||||
// the actual phone interface is available after the tutorial.
|
||||
// TODO: Is this the right place to set this flag?
|
||||
if (g_nancy->getGameType() == kGameTypeNancy9) {
|
||||
NancySceneState.setEventFlag(592, g_nancy->_false);
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 numButtons = 12;
|
||||
uint16 maxNumButtons = _isNewPhone ? 20 : 12;
|
||||
|
||||
if (_isNewPhone) {
|
||||
_hasDisplay = stream.readByte();
|
||||
_displayFont = stream.readUint16LE();
|
||||
readFilename(stream, _displayAnimName);
|
||||
_displayAnimFrameTime = stream.readUint32LE();
|
||||
uint16 numFrames = stream.readUint16LE();
|
||||
readRectArray(stream, _displaySrcs, numFrames, 10);
|
||||
readRect(stream, _displayDest);
|
||||
_dialAutomatically = stream.readByte();
|
||||
|
||||
numButtons = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRectArray(stream, _srcRects, numButtons, maxNumButtons);
|
||||
readRectArray(stream, _destRects, numButtons, maxNumButtons);
|
||||
|
||||
if (_isNewPhone) {
|
||||
readRect(stream, _dirHighlightSrc);
|
||||
readRect(stream, _dialHighlightSrc);
|
||||
|
||||
_upDirButtonID = stream.readUint16LE();
|
||||
_downDirButtonID = stream.readUint16LE();
|
||||
_dialButtonID = stream.readUint16LE();
|
||||
_dirButtonID = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _displayDialingSrc);
|
||||
}
|
||||
|
||||
if (!_isNewPhone) {
|
||||
_genericDialogueSound.readNormal(stream);
|
||||
_genericButtonSound.readNormal(stream);
|
||||
_ringSound.readNormal(stream);
|
||||
_dialToneSound.readNormal(stream);
|
||||
_dialAgainSound.readNormal(stream);
|
||||
_hangUpSound.readNormal(stream);
|
||||
} else {
|
||||
_ringSound.readNormal(stream);
|
||||
_dialToneSound.readNormal(stream);
|
||||
_preCallSound.readNormal(stream);
|
||||
_hangUpSound.readNormal(stream);
|
||||
_genericButtonSound.readNormal(stream);
|
||||
}
|
||||
|
||||
readFilenameArray(stream, _buttonSoundNames, numButtons);
|
||||
stream.skip(33 * (maxNumButtons - numButtons));
|
||||
|
||||
char textBuf[200];
|
||||
if (!_isNewPhone) {
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
_addressBookString = textBuf;
|
||||
} else {
|
||||
_dialAgainSound.readNormal(stream);
|
||||
}
|
||||
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
_dialAgainString = textBuf;
|
||||
|
||||
_reloadScene.readData(stream);
|
||||
stream.skip(1);
|
||||
_exitScene.readData(stream);
|
||||
stream.skip(1);
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
uint numCalls = stream.readUint16LE();
|
||||
|
||||
_calls.resize(numCalls);
|
||||
for (uint i = 0; i < numCalls; ++i) {
|
||||
PhoneCall &call = _calls[i];
|
||||
|
||||
if (_isNewPhone) {
|
||||
call.directoryDisplayCondition = stream.readSint16LE();
|
||||
}
|
||||
|
||||
call.phoneNumber.resize(11);
|
||||
for (uint j = 0; j < 11; ++j) {
|
||||
call.phoneNumber[j] = stream.readByte();
|
||||
}
|
||||
|
||||
if (!_isNewPhone) {
|
||||
readFilename(stream, call.soundName);
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
call.text = textBuf;
|
||||
} else {
|
||||
readRect(stream, call.displaySrc);
|
||||
}
|
||||
|
||||
call.sceneChange.readData(stream);
|
||||
stream.skip(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_dialToneSound);
|
||||
g_nancy->_sound->playSound(_dialToneSound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_addressBookString);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_callState) {
|
||||
case kWaiting:
|
||||
if (_isNewPhone && !_animIsStopped) {
|
||||
if (g_nancy->getTotalPlayTime() > _displayAnimEnd) {
|
||||
if (_displayAnimEnd == 0) {
|
||||
_displayAnimEnd = g_nancy->getTotalPlayTime() + _displayAnimFrameTime;
|
||||
} else {
|
||||
_displayAnimEnd += _displayAnimFrameTime;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_animImage, _displaySrcs[_displayAnimFrame], _displayDest);
|
||||
_needsRedraw = true;
|
||||
++_displayAnimFrame;
|
||||
|
||||
if (_displayAnimFrame >= _displaySrcs.size()) {
|
||||
_displayAnimFrame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_checkNumbers) {
|
||||
// Pressed a new button, check all numbers for match
|
||||
// We do this before going to the ringing state to support nancy4's voice mail system,
|
||||
// where call numbers can be 1 digit long
|
||||
for (uint i = 0; i < _calls.size(); ++i) {
|
||||
auto &call = _calls[i];
|
||||
bool invalid = false;
|
||||
|
||||
for (uint j = 0; j < _calledNumber.size(); ++j) {
|
||||
if (_calledNumber[j] != call.phoneNumber[j]) {
|
||||
// Invalid number, move onto next
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We do not want to check for a terminator if the dialed number is of
|
||||
// appropriate size (7 digits, or 11 when the number starts with '1')
|
||||
bool checkNextDigit = true;
|
||||
if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) {
|
||||
checkNextDigit = false;
|
||||
}
|
||||
|
||||
if (!invalid && checkNextDigit) {
|
||||
// Check if the next digit in the phone number is '10' (star). Presumably, that will never
|
||||
// be contained in a valid phone number
|
||||
if (_calls[i].phoneNumber[_calledNumber.size()] != 10) {
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!invalid) {
|
||||
_selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldRing = false;
|
||||
|
||||
if (_selected == -1) {
|
||||
// Did not find a suitable match, check if the dialed number is above allowed size
|
||||
if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) {
|
||||
shouldRing = true;
|
||||
}
|
||||
} else {
|
||||
shouldRing = true;
|
||||
}
|
||||
|
||||
if (shouldRing) {
|
||||
if (_ringSound.name != "NO SOUND") {
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.blitFrom(_image, _displayDialingSrc, _displayDest);
|
||||
_needsRedraw = true;
|
||||
} else {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(g_nancy->getStaticData().ringingText);
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_ringSound);
|
||||
g_nancy->_sound->playSound(_ringSound);
|
||||
}
|
||||
|
||||
_callState = kRinging;
|
||||
}
|
||||
|
||||
_checkNumbers = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case kButtonPress:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_genericButtonSound)) {
|
||||
g_nancy->_sound->stopSound(_genericButtonSound);
|
||||
_drawSurface.fillRect(_destRects[_buttonLastPushed], g_nancy->_graphics->getTransColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
if (_isShowingDirectory) {
|
||||
_drawSurface.blitFrom(_image, _dirHighlightSrc, _destRects[_dirButtonID]);
|
||||
_drawSurface.blitFrom(_image, _calls[_displayedDirectory].displaySrc, _displayDest);
|
||||
} else if (_dirButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_buttonLastPushed = -1;
|
||||
_callState = kWaiting;
|
||||
}
|
||||
|
||||
break;
|
||||
case kRinging:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_ringSound)) {
|
||||
g_nancy->_sound->stopSound(_ringSound);
|
||||
|
||||
if (_selected != -1) {
|
||||
// Called a valid number
|
||||
|
||||
if (_preCallSound.name == "NO SOUND") {
|
||||
// Old phone, go directly to call
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_calls[_selected].text);
|
||||
|
||||
_genericDialogueSound.name = _calls[_selected].soundName;
|
||||
g_nancy->_sound->loadSound(_genericDialogueSound);
|
||||
g_nancy->_sound->playSound(_genericDialogueSound);
|
||||
_callState = kCall;
|
||||
} else {
|
||||
// New phone, play a short sound of phone being picked up
|
||||
g_nancy->_sound->loadSound(_preCallSound);
|
||||
g_nancy->_sound->playSound(_preCallSound);
|
||||
_callState = kPreCall;
|
||||
}
|
||||
} else {
|
||||
// Called an invalid number
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_dialAgainString);
|
||||
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_dialButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dialButtonID], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
_calledNumber.clear();
|
||||
|
||||
g_nancy->_sound->loadSound(_dialAgainSound);
|
||||
g_nancy->_sound->playSound(_dialAgainSound);
|
||||
_callState = kBadNumber;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case kPreCall:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_preCallSound)) {
|
||||
g_nancy->_sound->stopSound(_preCallSound);
|
||||
|
||||
if (!_calls[_selected].text.empty()) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_calls[_selected].text);
|
||||
}
|
||||
|
||||
_genericDialogueSound.name = _calls[_selected].soundName;
|
||||
g_nancy->_sound->loadSound(_genericDialogueSound);
|
||||
g_nancy->_sound->playSound(_genericDialogueSound);
|
||||
_callState = kCall;
|
||||
}
|
||||
|
||||
break;
|
||||
case kBadNumber:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_dialAgainSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kCall:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_genericDialogueSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kHangUp:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_hangUpSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_callState) {
|
||||
case kBadNumber:
|
||||
_reloadScene.execute();
|
||||
_calledNumber.clear();
|
||||
_state = kRun;
|
||||
_callState = kWaiting;
|
||||
|
||||
break;
|
||||
case kCall: {
|
||||
PhoneCall &call = _calls[_selected];
|
||||
|
||||
// Make sure we don't get stuck here. Happens in nancy3 when calling George's number
|
||||
// Check ignored in nancy1 since the HintSystem AR is in the same scene as the Telephone
|
||||
if (call.sceneChange._sceneChange.sceneID == kNoScene && g_nancy->getGameType() != kGameTypeNancy1) {
|
||||
call.sceneChange._sceneChange = NancySceneState.getSceneInfo();
|
||||
}
|
||||
|
||||
call.sceneChange.execute();
|
||||
|
||||
break;
|
||||
}
|
||||
case kHangUp:
|
||||
_exitScene.execute();
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_hangUpSound);
|
||||
g_nancy->_sound->stopSound(_genericDialogueSound);
|
||||
g_nancy->_sound->stopSound(_genericButtonSound);
|
||||
g_nancy->_sound->stopSound(_dialAgainSound);
|
||||
g_nancy->_sound->stopSound(_ringSound);
|
||||
g_nancy->_sound->stopSound(_dialToneSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::handleInput(NancyInput &input) {
|
||||
int buttonNr = -1;
|
||||
// Cursor gets changed regardless of state
|
||||
for (int i = 0; i < (int)_destRects.size(); ++i) {
|
||||
// Dial button is an exception
|
||||
if (i == _dialButtonID && !_calledNumber.size() && !_isShowingDirectory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
buttonNr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_callState != kWaiting && _callState != kRinging) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->loadSound(_hangUpSound);
|
||||
g_nancy->_sound->playSound(_hangUpSound);
|
||||
|
||||
_callState = kHangUp;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_callState != kWaiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonNr != -1) {
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_dialToneSound)) {
|
||||
g_nancy->_sound->stopSound(_dialToneSound);
|
||||
}
|
||||
|
||||
// Handle non-digit numbers
|
||||
bool directorySwitch = false;
|
||||
bool changeDirectoryEntry = false;
|
||||
int dirEntryDelta = 1;
|
||||
if (_dialButtonID != -1 && buttonNr == _dialButtonID) {
|
||||
if (_isShowingDirectory) {
|
||||
_calledNumber = _calls[_displayedDirectory].phoneNumber;
|
||||
while (_calledNumber.back() == 10) {
|
||||
_calledNumber.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
_checkNumbers = true;
|
||||
|
||||
// Dial button doesn't make sound, and doesn't get pressed down
|
||||
_drawSurface.blitFrom(_image, _dialHighlightSrc, _destRects[_dialButtonID]);
|
||||
|
||||
if (_dirButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_animIsStopped = true;
|
||||
return;
|
||||
} else if (_upDirButtonID != -1 && buttonNr == _upDirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
} else {
|
||||
++_displayedDirectory;
|
||||
changeDirectoryEntry = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else if (_downDirButtonID != -1 && buttonNr == _downDirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
} else {
|
||||
--_displayedDirectory;
|
||||
dirEntryDelta = -1;
|
||||
changeDirectoryEntry = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else if (_dirButtonID != -1 && buttonNr == _dirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else {
|
||||
if (_isShowingDirectory || !_calledNumber.size()) {
|
||||
_isShowingDirectory = false;
|
||||
_displayedDirectory = 0;
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_calledNumber.push_back(buttonNr);
|
||||
_checkNumbers = _dialAutomatically;
|
||||
_animIsStopped = true;
|
||||
|
||||
if (_calledNumber.size() > 11) {
|
||||
_calledNumber.clear();
|
||||
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
} else if (_isNewPhone) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
}
|
||||
|
||||
_checkNumbers = false;
|
||||
}
|
||||
|
||||
if (_isNewPhone && _calledNumber.size()) {
|
||||
Common::String numberString;
|
||||
for (uint j = 0; j < _calledNumber.size(); ++j) {
|
||||
numberString += '0' + _calledNumber[j];
|
||||
}
|
||||
|
||||
if (_hasDisplay) {
|
||||
_font->drawString(&_drawSurface, numberString, _displayDest.left + 19, _displayDest.top + 21 - _font->getFontHeight(),
|
||||
_displayDest.width() - 20, 0);
|
||||
} else {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(numberString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (directorySwitch) {
|
||||
// Handle switch to directory mode
|
||||
_isShowingDirectory = true;
|
||||
changeDirectoryEntry = true;
|
||||
_calledNumber.clear();
|
||||
}
|
||||
|
||||
if (changeDirectoryEntry) {
|
||||
int start = _displayedDirectory;
|
||||
|
||||
do {
|
||||
if (_displayedDirectory >= (int)_calls.size()) {
|
||||
_displayedDirectory = 0;
|
||||
} else if (_displayedDirectory < 0) {
|
||||
_displayedDirectory = _calls.size() - 1;
|
||||
}
|
||||
|
||||
if (_calls[_displayedDirectory].directoryDisplayCondition == kEvNoEvent) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (NancySceneState.getEventFlag(_calls[_displayedDirectory].directoryDisplayCondition, g_nancy->_true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
_displayedDirectory += dirEntryDelta;
|
||||
} while (_displayedDirectory != start);
|
||||
}
|
||||
|
||||
_genericButtonSound.name = _buttonSoundNames[buttonNr];
|
||||
g_nancy->_sound->loadSound(_genericButtonSound);
|
||||
g_nancy->_sound->playSound(_genericButtonSound);
|
||||
|
||||
_drawSurface.blitFrom(_image, _srcRects[buttonNr], _destRects[buttonNr]);
|
||||
_needsRedraw = true;
|
||||
|
||||
_displayAnimEnd = 0;
|
||||
_displayAnimFrame = 0;
|
||||
|
||||
_buttonLastPushed = buttonNr;
|
||||
_callState = kButtonPress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
130
engines/nancy/action/puzzle/telephone.h
Normal file
130
engines/nancy/action/puzzle/telephone.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TELEPHONE_H
|
||||
#define NANCY_ACTION_TELEPHONE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class Font;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class Telephone : public RenderActionRecord {
|
||||
public:
|
||||
struct PhoneCall {
|
||||
Common::Array<byte> phoneNumber;
|
||||
Common::String soundName;
|
||||
Common::String text;
|
||||
SceneChangeWithFlag sceneChange;
|
||||
|
||||
// NewPhone members
|
||||
int16 directoryDisplayCondition = -1;
|
||||
Common::Rect displaySrc;
|
||||
};
|
||||
|
||||
enum CallState { kWaiting, kButtonPress, kRinging, kBadNumber, kPreCall, kCall, kHangUp };
|
||||
|
||||
Telephone(bool isNewPhone) :
|
||||
RenderActionRecord(7),
|
||||
_callState(kWaiting),
|
||||
_buttonLastPushed(-1),
|
||||
_selected(-1),
|
||||
_checkNumbers(false),
|
||||
_font(nullptr),
|
||||
_animIsStopped(false),
|
||||
_isNewPhone(isNewPhone) {}
|
||||
virtual ~Telephone() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return _isNewPhone ? "NewPhone" : "Telephone"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
SoundDescription _genericDialogueSound;
|
||||
SoundDescription _genericButtonSound;
|
||||
SoundDescription _ringSound;
|
||||
SoundDescription _dialToneSound;
|
||||
SoundDescription _dialAgainSound;
|
||||
SoundDescription _hangUpSound;
|
||||
Common::Array<Common::String> _buttonSoundNames;
|
||||
Common::String _addressBookString;
|
||||
Common::String _dialAgainString;
|
||||
SceneChangeWithFlag _reloadScene;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
Common::Array<PhoneCall> _calls;
|
||||
|
||||
// NewPhone properties
|
||||
bool _hasDisplay = false;
|
||||
uint16 _displayFont = 0;
|
||||
Common::Path _displayAnimName;
|
||||
uint32 _displayAnimFrameTime = 0;
|
||||
Common::Array<Common::Rect> _displaySrcs;
|
||||
Common::Rect _displayDest;
|
||||
|
||||
bool _dialAutomatically = true;
|
||||
|
||||
Common::Rect _dirHighlightSrc;
|
||||
Common::Rect _dialHighlightSrc;
|
||||
|
||||
int16 _upDirButtonID = -1;
|
||||
int16 _downDirButtonID = -1;
|
||||
int16 _dialButtonID = -1;
|
||||
int16 _dirButtonID = -1;
|
||||
|
||||
Common::Rect _displayDialingSrc;
|
||||
|
||||
SoundDescription _preCallSound;
|
||||
|
||||
Common::Array<byte> _calledNumber;
|
||||
Graphics::ManagedSurface _image;
|
||||
Graphics::ManagedSurface _animImage;
|
||||
CallState _callState;
|
||||
int _buttonLastPushed;
|
||||
int _selected;
|
||||
bool _checkNumbers;
|
||||
bool _animIsStopped;
|
||||
|
||||
uint32 _displayAnimEnd = 0;
|
||||
uint16 _displayAnimFrame = 0;
|
||||
int16 _displayedDirectory = 0;
|
||||
bool _isShowingDirectory = false;
|
||||
|
||||
const Font *_font;
|
||||
|
||||
bool _isNewPhone;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TELEPHONE_H
|
||||
305
engines/nancy/action/puzzle/towerpuzzle.cpp
Normal file
305
engines/nancy/action/puzzle/towerpuzzle.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/towerpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TowerPuzzle::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);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void TowerPuzzle::registerGraphics() {
|
||||
_heldRing.registerGraphics();
|
||||
RenderObject::registerGraphics();
|
||||
}
|
||||
|
||||
void TowerPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numRingsByDifficulty.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_numRingsByDifficulty[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
readRectArray(stream, _droppedRingSrcs, 6);
|
||||
readRectArray(stream, _heldRingSrcs, 6);
|
||||
|
||||
readRectArray(stream, _hotspots, 3);
|
||||
|
||||
_destRects.resize(6);
|
||||
for (uint ringID = 0; ringID < 6; ++ringID) {
|
||||
_destRects[ringID].resize(3);
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
// Biggest ring can only be in bottom position,
|
||||
// so it only has one rect per pole; second-biggest can
|
||||
// be in bottom-most and one position above it, so it has
|
||||
// two rects per pole, etc. Skipped data is array of 0xFF.
|
||||
readRectArray(stream, _destRects[ringID][poleID], ringID + 1, 6);
|
||||
}
|
||||
}
|
||||
|
||||
_takeSound.readNormal(stream);
|
||||
_dropSound.readNormal(stream);
|
||||
|
||||
_solveExitScene._sceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_solveSound.readNormal(stream);
|
||||
_solveExitScene._flag.label = stream.readSint16LE();
|
||||
_solveExitScene._flag.flag = stream.readByte();
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TowerPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
_numRings = _numRingsByDifficulty[NancySceneState.getDifficulty()];
|
||||
|
||||
if (!_puzzleState->playerHasTriedPuzzle) {
|
||||
_puzzleState->order.clear();
|
||||
_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
|
||||
for (uint i = 0; i < _numRings; ++i) {
|
||||
_puzzleState->order[0][i] = i;
|
||||
}
|
||||
_puzzleState->playerHasTriedPuzzle = true;
|
||||
}
|
||||
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
for (uint pos = 0; pos < _numRings; ++pos) {
|
||||
if (_puzzleState->order[poleID][pos] == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drawRing(poleID, pos, _puzzleState->order[poleID][pos]);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
g_nancy->_sound->loadSound(_takeSound);
|
||||
g_nancy->_sound->loadSound(_dropSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved :
|
||||
for (uint i = 0; i < _numRings; ++i) {
|
||||
// Win condition is valid for both middle and right pole
|
||||
if (_puzzleState->order[1][i] != (int8)i && _puzzleState->order[2][i] != (int8)i) {
|
||||
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:
|
||||
_solveExitScene.execute();
|
||||
_puzzleState->playerHasTriedPuzzle = false;
|
||||
_puzzleState->order.clear();
|
||||
_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_takeSound);
|
||||
g_nancy->_sound->stopSound(_dropSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TowerPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun && _solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: this is a click-and-drag puzzle
|
||||
|
||||
// Check if mouse is above a pole hotspot
|
||||
// and change the cursor if needed
|
||||
int hoveredPoleID = -1;
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[poleID]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
hoveredPoleID = poleID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_heldRingID == -1) {
|
||||
// Not holding a ring
|
||||
|
||||
// First, check the exit hotspot
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, exit
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need to pick up a ring
|
||||
// Behavior is the same as original engine, where clicking outside a hotspot
|
||||
// and dragging the mouse inside while holding the click still triggers
|
||||
if (hoveredPoleID != -1 && (input.input & NancyInput::kLeftMouseButtonHeld)) {
|
||||
// Find the position of the topmost ring
|
||||
int ringPos;
|
||||
for (ringPos = 5; ringPos > -1; --ringPos) {
|
||||
if (_puzzleState->order[hoveredPoleID][ringPos] != -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ringPos == -1) {
|
||||
// Pole contains no rings, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Redraw so the ring isn't visible anymore
|
||||
drawRing(hoveredPoleID, ringPos, _puzzleState->order[hoveredPoleID][ringPos], true);
|
||||
|
||||
if (ringPos > 0) {
|
||||
drawRing(hoveredPoleID, ringPos - 1, _puzzleState->order[hoveredPoleID][ringPos - 1]);
|
||||
}
|
||||
|
||||
// Change the data
|
||||
SWAP<int8>(_heldRingID, _puzzleState->order[hoveredPoleID][ringPos]);
|
||||
_heldRingPoleID = hoveredPoleID;
|
||||
|
||||
// Show the held ring
|
||||
_heldRing._drawSurface.create(_image, _heldRingSrcs[_heldRingID]);
|
||||
_heldRing.setVisible(true);
|
||||
_heldRing.setTransparent(true);
|
||||
_heldRing.pickUp();
|
||||
|
||||
g_nancy->_sound->playSound(_takeSound);
|
||||
}
|
||||
}
|
||||
|
||||
if (_heldRingID != -1) {
|
||||
_heldRing.handleInput(input);
|
||||
|
||||
// Holding a ring, check if it has just been dropped
|
||||
if ((input.input & NancyInput::kLeftMouseButtonUp) || !(input.input & NancyInput::kLeftMouseButtonHeld)) {
|
||||
// Check if dropped over a pole hotspot
|
||||
// If not, return to old pole; if yes, move to new one
|
||||
uint returnToPole = hoveredPoleID == -1 ? _heldRingPoleID : hoveredPoleID;
|
||||
|
||||
// Find the new position of the ring
|
||||
uint newPos;
|
||||
for (newPos = 0; newPos < 6; ++newPos) {
|
||||
if (_puzzleState->order[returnToPole][newPos] == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the player can't place a larger ring on top of a smaller one
|
||||
if (newPos > 0 && _puzzleState->order[returnToPole][newPos - 1] > _heldRingID) {
|
||||
returnToPole = _heldRingPoleID;
|
||||
|
||||
for (newPos = 0; newPos < 6; ++newPos) {
|
||||
if (_puzzleState->order[returnToPole][newPos] == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the new ring in its place
|
||||
drawRing(returnToPole, newPos, _heldRingID);
|
||||
|
||||
// Change the data
|
||||
SWAP<int8>(_heldRingID, _puzzleState->order[returnToPole][newPos]);
|
||||
_heldRingPoleID = -1;
|
||||
|
||||
g_nancy->_sound->playSound(_dropSound);
|
||||
|
||||
// Hide the held ring
|
||||
_heldRing.setVisible(false);
|
||||
_heldRing.putDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TowerPuzzle::drawRing(uint poleID, uint position, uint ringID, bool clear) {
|
||||
_needsRedraw = true;
|
||||
|
||||
if (clear) {
|
||||
// Just clear the ring, leaving a hole in the surface
|
||||
// that needs to be filled by redrawing the ring below
|
||||
_drawSurface.fillRect(_destRects[ringID][poleID][position], _drawSurface.getTransparentColor());
|
||||
return;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _droppedRingSrcs[ringID], _destRects[ringID][poleID][position]);
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
82
engines/nancy/action/puzzle/towerpuzzle.h
Normal file
82
engines/nancy/action/puzzle/towerpuzzle.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TOWERPUZZLE_H
|
||||
#define NANCY_ACTION_TOWERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct TowerPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class TowerPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
TowerPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TowerPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TowerPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawRing(uint poleID, uint position, uint ringID, bool clear = false);
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<uint16> _numRingsByDifficulty;
|
||||
|
||||
Common::Array<Common::Rect> _droppedRingSrcs;
|
||||
Common::Array<Common::Rect> _heldRingSrcs;
|
||||
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<Common::Array<Common::Array<Common::Rect>>> _destRects; // [ringID][poleID][position]
|
||||
|
||||
SoundDescription _takeSound;
|
||||
SoundDescription _dropSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Misc::MouseFollowObject _heldRing;
|
||||
int8 _heldRingID = -1;
|
||||
int8 _heldRingPoleID = -1;
|
||||
SolveState _solveState = kNotSolved;
|
||||
TowerPuzzleData *_puzzleState = nullptr;
|
||||
uint _numRings = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TOWERPUZZLE_H
|
||||
316
engines/nancy/action/puzzle/turningpuzzle.cpp
Normal file
316
engines/nancy/action/puzzle/turningpuzzle.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/turningpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TurningPuzzle::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);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TurningPuzzle::updateGraphics() {
|
||||
if (_state == kBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solveState == kWaitForAnimation) {
|
||||
if (g_nancy->getTotalPlayTime() > _nextTurnTime) {
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
|
||||
if ( (_turnFrameID == 0 && _solveAnimFace == 0) ||
|
||||
(_turnFrameID == 1 && _solveAnimFace > 0 && (int)_solveAnimFace < _numFaces - 1)) {
|
||||
g_nancy->_sound->playSound(_turnSound);
|
||||
}
|
||||
|
||||
if (_turnFrameID >= _numFramesPerTurn) {
|
||||
++_solveAnimFace;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime += 1000 * _solveDelayBetweenTurns;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _currentOrder.size(); ++i) {
|
||||
uint faceID = _currentOrder[i] + _solveAnimFace;
|
||||
if (faceID >= _numFaces) {
|
||||
faceID -= _numFaces;
|
||||
}
|
||||
|
||||
drawObject(i, faceID, _turnFrameID);
|
||||
}
|
||||
|
||||
if ((int)_solveAnimFace >= _numFaces - 1) {
|
||||
_solveAnimFace = 0;
|
||||
++_solveAnimLoop;
|
||||
|
||||
if (_solveAnimLoop >= _solveAnimationNumRepeats) {
|
||||
_solveState = kWaitBeforeSound;
|
||||
_objectCurrentlyTurning = -1;
|
||||
}
|
||||
}
|
||||
|
||||
++_turnFrameID;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
if (g_nancy->getTotalPlayTime() > _nextTurnTime) {
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
++_turnFrameID;
|
||||
|
||||
uint faceID = _currentOrder[_objectCurrentlyTurning];
|
||||
uint frameID = _turnFrameID;
|
||||
|
||||
if (frameID == _numFramesPerTurn && (int)faceID == _numFaces - 1) {
|
||||
faceID = frameID = 0;
|
||||
}
|
||||
|
||||
// Draw clicked spindle
|
||||
drawObject(_objectCurrentlyTurning, faceID, frameID);
|
||||
|
||||
// Draw linked spindles
|
||||
for (uint i = 0; i < _links[_objectCurrentlyTurning].size(); ++i) {
|
||||
faceID = _currentOrder[_links[_objectCurrentlyTurning][i] - 1];
|
||||
frameID = _turnFrameID;
|
||||
|
||||
if (frameID == _numFramesPerTurn && (int)faceID == _numFaces - 1) {
|
||||
faceID = frameID = 0;
|
||||
}
|
||||
|
||||
drawObject(_links[_objectCurrentlyTurning][i] - 1, faceID, frameID);
|
||||
}
|
||||
|
||||
if (_turnFrameID >= _numFramesPerTurn) {
|
||||
turnLogic(_objectCurrentlyTurning);
|
||||
_objectCurrentlyTurning = -1;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
uint numSpindles = stream.readUint16LE();
|
||||
_numFaces = stream.readUint16LE();
|
||||
_numFramesPerTurn = stream.readUint16LE();
|
||||
|
||||
_startPositions.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
_startPositions[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((16 - numSpindles) * 2);
|
||||
|
||||
readRectArray(stream, _destRects, numSpindles, 16);
|
||||
readRectArray(stream, _hotspots, numSpindles, 16);
|
||||
|
||||
_separateRows = stream.readByte();
|
||||
|
||||
_startPos.x = stream.readSint32LE();
|
||||
_startPos.y = stream.readSint32LE();
|
||||
_srcIncrement.x = stream.readSint16LE();
|
||||
_srcIncrement.y = stream.readSint16LE();
|
||||
|
||||
_links.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
for (uint j = 0; j < 4; ++j) {
|
||||
uint16 val = stream.readUint16LE();
|
||||
if (val == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
_links[i].push_back(val);
|
||||
}
|
||||
|
||||
if (_links[i].size() < 4) {
|
||||
stream.skip((4 - _links[i].size() - 1) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
stream.skip((16 - numSpindles) * 4 * 2);
|
||||
|
||||
_solveDelayBetweenTurns = stream.readUint16LE();
|
||||
_solveAnimate = stream.readByte();
|
||||
_solveAnimationNumRepeats = stream.readUint16LE();
|
||||
|
||||
_turnSound.readNormal(stream);
|
||||
|
||||
_correctOrder.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
_correctOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((16 - numSpindles) * 2);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TurningPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_turnSound);
|
||||
_currentOrder = _startPositions;
|
||||
for (uint i = 0; i < _currentOrder.size(); ++i) {
|
||||
drawObject(i, _currentOrder[i], 0);
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentOrder == _correctOrder) {
|
||||
_state = kActionTrigger;
|
||||
if (_solveAnimate) {
|
||||
_solveState = kWaitForAnimation;
|
||||
} else {
|
||||
_solveState = kWaitForSound;
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
}
|
||||
_objectCurrentlyTurning = -1;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
switch (_solveState) {
|
||||
case kWaitForAnimation :
|
||||
if (_nextTurnTime == 0) {
|
||||
_solveState = kWaitForSound;
|
||||
}
|
||||
return;
|
||||
case kWaitBeforeSound :
|
||||
if (_solveSoundDelayTime == 0) {
|
||||
_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
|
||||
} else if (g_nancy->getTotalPlayTime() > _solveSoundDelayTime) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_solveState = kWaitForSound;
|
||||
}
|
||||
|
||||
return;
|
||||
case kWaitForSound :
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound) || g_nancy->_sound->isSoundPlaying(_turnSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
break;
|
||||
case kNotSolved :
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_turnSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::handleInput(NancyInput &input) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _hotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_turnSound);
|
||||
_objectCurrentlyTurning = i;
|
||||
}
|
||||
|
||||
// fixes nancy4 scene 4308
|
||||
input.eatMouseInput();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::drawObject(uint objectID, uint faceID, uint frameID) {
|
||||
Common::Rect srcRect = _destRects[objectID];
|
||||
srcRect.moveTo(_startPos);
|
||||
Common::Point inc(_srcIncrement.x == 1 ? srcRect.width() : _srcIncrement.x, _srcIncrement.y == -2 ? srcRect.height() : _srcIncrement.y);
|
||||
srcRect.translate( inc.x * frameID + inc.x * _numFramesPerTurn * faceID,
|
||||
_separateRows ? inc.y * objectID : 0);
|
||||
|
||||
_drawSurface.blitFrom(_image, srcRect, _destRects[objectID]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void TurningPuzzle::turnLogic(uint objectID) {
|
||||
++_currentOrder[objectID];
|
||||
if (_currentOrder[objectID] >= _numFaces) {
|
||||
_currentOrder[objectID] = 0;
|
||||
}
|
||||
|
||||
for (uint j = 0; j < _links[objectID].size(); ++j) {
|
||||
++_currentOrder[_links[objectID][j] - 1];
|
||||
if (_currentOrder[_links[objectID][j] - 1] >= _numFaces) {
|
||||
_currentOrder[_links[objectID][j] - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
101
engines/nancy/action/puzzle/turningpuzzle.h
Normal file
101
engines/nancy/action/puzzle/turningpuzzle.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TURNINGPUZZLE_H
|
||||
#define NANCY_ACTION_TURNINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles a specific type of puzzle where clicking an object rotates it,
|
||||
// as well as several other objects linked to it. Examples are the sun/moon
|
||||
// and staircase spindle puzzles in nancy3
|
||||
class TurningPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForAnimation, kWaitBeforeSound, kWaitForSound };
|
||||
TurningPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TurningPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TurningPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawObject(uint objectID, uint faceID, uint frameID);
|
||||
void turnLogic(uint objectID);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numFaces = 0;
|
||||
uint16 _numFramesPerTurn = 0;
|
||||
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<uint16> _startPositions;
|
||||
|
||||
bool _separateRows = false;
|
||||
|
||||
Common::Point _startPos;
|
||||
Common::Point _srcIncrement;
|
||||
|
||||
Common::Array<Common::Array<uint16>> _links;
|
||||
|
||||
uint16 _solveDelayBetweenTurns = 0;
|
||||
bool _solveAnimate = false;
|
||||
uint16 _solveAnimationNumRepeats = 0;
|
||||
|
||||
SoundDescription _turnSound;
|
||||
|
||||
Common::Array<uint16> _correctOrder;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<uint16> _currentOrder;
|
||||
|
||||
uint32 _solveSoundDelayTime = 0;
|
||||
uint32 _nextTurnTime = 0;
|
||||
int32 _objectCurrentlyTurning = -1;
|
||||
uint32 _turnFrameID = 0;
|
||||
|
||||
uint32 _solveAnimLoop = 0;
|
||||
uint32 _solveAnimFace = 0;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TURNINGPUZZLE_H
|
||||
181
engines/nancy/action/puzzle/twodialpuzzle.cpp
Normal file
181
engines/nancy/action/puzzle/twodialpuzzle.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/twodialpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TwoDialPuzzle::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);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 num1 = stream.readUint16LE();
|
||||
uint16 num2 = stream.readUint16LE();
|
||||
|
||||
_isClockwise[0] = stream.readByte();
|
||||
_isClockwise[1] = stream.readByte();
|
||||
|
||||
_startPositions[0] = stream.readUint16LE();
|
||||
_startPositions[1] = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _hotspots[0]);
|
||||
readRect(stream, _hotspots[1]);
|
||||
readRect(stream, _dests[0]);
|
||||
readRect(stream, _dests[1]);
|
||||
readRectArray(stream, _srcs[0], num1, 20);
|
||||
readRectArray(stream, _srcs[1], num2, 20);
|
||||
|
||||
_correctPositions[0] = stream.readUint16LE();
|
||||
_correctPositions[1] = stream.readUint16LE();
|
||||
|
||||
_rotateSounds[0].readNormal(stream);
|
||||
_rotateSounds[1].readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_rotateSounds[0]);
|
||||
g_nancy->_sound->loadSound(_rotateSounds[1]);
|
||||
_currentPositions[0] = _startPositions[0];
|
||||
_currentPositions[1] = _startPositions[1];
|
||||
|
||||
_drawSurface.blitFrom(_image, _srcs[0][_currentPositions[0]], _dests[0]);
|
||||
_drawSurface.blitFrom(_image, _srcs[1][_currentPositions[1]], _dests[1]);
|
||||
_needsRedraw = true;
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) || g_nancy->_sound->isSoundPlaying(_rotateSounds[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((uint)_currentPositions[0] == _correctPositions[0] && (uint)_currentPositions[1] == _correctPositions[1]) {
|
||||
_state = kActionTrigger;
|
||||
_isSolved = true;
|
||||
_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_isSolved) {
|
||||
if (_solveSoundDelayTime != 0) {
|
||||
if (g_nancy->getTotalPlayTime() < _solveSoundDelayTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveSoundDelayTime = 0;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
return;
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
}
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_rotateSounds[0]);
|
||||
g_nancy->_sound->stopSound(_rotateSounds[1]);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::handleInput(NancyInput &input) {
|
||||
bool canClick = (_state == kRun) && !g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) && !g_nancy->_sound->isSoundPlaying(_rotateSounds[1]);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i <= 1; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(_isClockwise[i] ? CursorManager::kRotateCW : CursorManager::kRotateCCW);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_currentPositions[i] += _isClockwise[i] ? -1 : 1;
|
||||
|
||||
if (_currentPositions[i] < 0) {
|
||||
_currentPositions[i] = _srcs[i].size() - 1;
|
||||
} else if ((uint)_currentPositions[i] >= _srcs[i].size()) {
|
||||
_currentPositions[i] = 0;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_rotateSounds[i]);
|
||||
_drawSurface.fillRect(_dests[0].findIntersectingRect(_dests[1]), _drawSurface.getTransparentColor());
|
||||
|
||||
// Blit both dials just in case
|
||||
_drawSurface.blitFrom(_image, _srcs[0][_currentPositions[0]], _dests[0]);
|
||||
_drawSurface.blitFrom(_image, _srcs[1][_currentPositions[1]], _dests[1]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
78
engines/nancy/action/puzzle/twodialpuzzle.h
Normal file
78
engines/nancy/action/puzzle/twodialpuzzle.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TWODIALPUZZLE_H
|
||||
#define NANCY_ACTION_TWODIALPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Puzzle with two circular dials overlaid on top of each other. Each dial has one correct
|
||||
// position, and can only be rotated in one direction.
|
||||
class TwoDialPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
TwoDialPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TwoDialPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TwoDialPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
bool _isClockwise[2] = { false, false };
|
||||
uint16 _startPositions[2] = { 0, 0 };
|
||||
|
||||
Common::Rect _hotspots[2];
|
||||
Common::Rect _dests[2];
|
||||
Common::Array<Common::Rect> _srcs[2];
|
||||
|
||||
uint16 _correctPositions[2] = { 0, 0 };
|
||||
|
||||
SoundDescription _rotateSounds[2];
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int16 _currentPositions[2] = { 0, 0 };
|
||||
|
||||
bool _isSolved = false;
|
||||
uint32 _solveSoundDelayTime = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TWODIALPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/whalesurvivorpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/whalesurvivorpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/whalesurvivorpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void WhaleSurvivorPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Whale survivor puzzle");
|
||||
NancySceneState.setEventFlag(439, g_nancy->_true); // EV_Solved_Whale_Survivor
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 2926;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/whalesurvivorpuzzle.h
Normal file
51
engines/nancy/action/puzzle/whalesurvivorpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
#define NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Feeding frenzy puzzle in Nancy 9
|
||||
|
||||
class WhaleSurvivorPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
WhaleSurvivorPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~WhaleSurvivorPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "WhaleSurvivorPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
Reference in New Issue
Block a user