Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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 &current = _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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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