/* 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 .
*
*/
#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(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(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(_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(_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