[Scummvm-git-logs] scummvm master -> b2a3194aadbc6914d16b79097235cb2487e02059
fracturehill
noreply at scummvm.org
Sun Sep 3 15:38:04 UTC 2023
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
e9675a0e39 NANCY: Implement OrderItemsPuzzle
3279224042 NANCY: Show load button immediately after saving
b2a3194aad NANCY: Implement CollisionPuzzle
Commit: e9675a0e39e9333255ce54b079763081a6a05e4c
https://github.com/scummvm/scummvm/commit/e9675a0e39e9333255ce54b079763081a6a05e4c
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-03T18:37:48+03:00
Commit Message:
NANCY: Implement OrderItemsPuzzle
Implemented the OrderItemsPuzzle, which is an upgraded
version of OrderingPuzzle allowing for an interaction with
an inventory item.
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/orderingpuzzle.cpp
engines/nancy/action/orderingpuzzle.h
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index f0cd65855e2..3843d549734 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -210,6 +210,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
return new TurningPuzzle();
case 210:
return new SafeLockPuzzle();
+ case 212:
+ return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kOrderItems);
default:
error("Action Record type %i is invalid!", type);
return nullptr;
diff --git a/engines/nancy/action/orderingpuzzle.cpp b/engines/nancy/action/orderingpuzzle.cpp
index 99954ad4590..f29c33fc8cf 100644
--- a/engines/nancy/action/orderingpuzzle.cpp
+++ b/engines/nancy/action/orderingpuzzle.cpp
@@ -36,7 +36,18 @@ namespace Nancy {
namespace Action {
void OrderingPuzzle::init() {
- // Screen position is initialized in readData and fits exactly the bounds of all elements on screen.
+ 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]);
+ }
+
g_nancy->_resource->loadImage(_imageName, _image);
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphicsManager->getInputPixelFormat());
@@ -47,14 +58,15 @@ void OrderingPuzzle::init() {
}
setTransparent(true);
- setVisible(false);
- clearAllElements();
+ _drawSurface.clear(_drawSurface.getTransparentColor());
+ setVisible(true);
RenderObject::init();
}
void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
bool isPiano = _puzzleType == kPiano;
+ bool isOrderItems = _puzzleType == kOrderItems;
readFilename(stream, _imageName);
Common::Serializer ser(&stream, nullptr);
ser.setVersion(g_nancy->getGameType());
@@ -67,12 +79,25 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
ser.syncAsUint16LE(numElements);
}
- readRectArray(stream, _srcRects, numElements);
+ if (isOrderItems) {
+ ser.syncAsByte(_hasSecondState);
+ ser.syncAsByte(_itemsStayDown);
+ } else if (isPiano) {
+ _itemsStayDown = false;
+ }
+ readRectArray(stream, _down1Rects, numElements);
ser.skip(16 * (15 - numElements), kGameTypeNancy1);
- readRectArray(stream, _destRects, numElements);
+ if (isOrderItems) {
+ readRectArray(stream, _up2Rects, numElements);
+ ser.skip(16 * (15 - numElements));
+
+ readRectArray(stream, _down2Rects, numElements);
+ ser.skip(16 * (15 - numElements));
+ }
+ readRectArray(stream, _destRects, numElements);
ser.skip(16 * (15 - numElements), kGameTypeNancy1);
_hotspots.resize(numElements);
@@ -83,34 +108,43 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
_hotspots = _destRects;
}
- _drawnElements.resize(numElements, false);
- for (uint i = 0; i < numElements; ++i) {
- if (i == 0) {
- _screenPosition = _destRects[i];
- } else {
- _screenPosition.extend(_destRects[i]);
+ uint sequenceLength = 5;
+ ser.syncAsUint16LE(sequenceLength, kGameTypeNancy1);
+
+ _correctSequence.resize(sequenceLength);
+ for (uint i = 0; i < sequenceLength; ++i) {
+ switch (_puzzleType) {
+ case kOrdering:
+ ser.syncAsByte(_correctSequence[i]);
+ break;
+ case kPiano:
+ ser.syncAsUint16LE(_correctSequence[i]);
+ break;
+ case kOrderItems:
+ // For some reason, OrderItems labels starting from 1
+ ser.syncAsUint16LE(_correctSequence[i]);
+ --_correctSequence[i];
+ break;
}
}
+ ser.skip((15 - sequenceLength) * (_puzzleType == kOrdering ? 1 : 2), kGameTypeNancy1);
- if (ser.getVersion() == kGameTypeVampire) {
- _sequenceLength = 5;
- } else {
- ser.syncAsUint16LE(_sequenceLength);
- }
+ if (isOrderItems) {
+ uint numOverlays = 0;
+ ser.syncAsUint16LE(_state2InvItem);
+ ser.syncAsUint16LE(numOverlays);
- _correctSequence.resize(_sequenceLength);
- for (uint i = 0; i < _sequenceLength; ++i) {
- if (isPiano) {
- ser.syncAsUint16LE(_correctSequence[i]);
- } else {
- ser.syncAsByte(_correctSequence[i]);
- }
+ readRectArray(ser, _overlaySrcs, numOverlays);
+ readRectArray(ser, _overlayDests, numOverlays);
}
- ser.skip((15 - _sequenceLength) * (isPiano ? 2 : 1), kGameTypeNancy1);
+ if (ser.getVersion() > kGameTypeVampire) {
+ _pushDownSound.readNormal(stream);
- if (ser.getVersion() != kGameTypeVampire) {
- _clickSound.readNormal(stream);
+ if (isOrderItems) {
+ _itemSound.readNormal(stream);
+ _popUpSound.readNormal(stream);
+ }
}
_solveExitScene.readData(stream, ser.getVersion() == kGameTypeVampire);
@@ -118,6 +152,9 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
_solveSound.readNormal(stream);
_exitScene.readData(stream, ser.getVersion() == kGameTypeVampire);
readRect(stream, _exitHotspot);
+
+ _downItems.resize(numElements, false);
+ _secondStateItems.resize(numElements, false);
}
void OrderingPuzzle::execute() {
@@ -125,41 +162,87 @@ void OrderingPuzzle::execute() {
case kBegin:
init();
registerGraphics();
- if (g_nancy->getGameType () != kGameTypeVampire) {
- g_nancy->_sound->loadSound(_clickSound);
+ if (g_nancy->getGameType() > kGameTypeVampire) {
+ g_nancy->_sound->loadSound(_pushDownSound);
+ if (_puzzleType == kOrderItems) {
+ g_nancy->_sound->loadSound(_itemSound);
+ g_nancy->_sound->loadSound(_popUpSound);
+ }
}
- g_nancy->_sound->loadSound(_solveSound);
_state = kRun;
// fall through
case kRun:
switch (_solveState) {
case kNotSolved:
- if (_puzzleType == kOrdering) {
- if (_clickedSequence.size() < _sequenceLength) {
+ if (!_itemsStayDown) {
+ // Clear the pushed item
+ if (g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
return;
}
- for (uint i = 0; i < _sequenceLength; ++i) {
- if (_clickedSequence[i] != (int16)_correctSequence[i]) {
- if (_clickedSequence.size() > (g_nancy->getGameType() == kGameTypeVampire ? 4 : (uint)_sequenceLength + 1)) {
- clearAllElements();
- }
-
- return;
+ for (uint i = 0; i < _downItems.size(); ++i) {
+ if (_downItems[i]) {
+ popUp(i);
}
}
- } else {
- if (g_nancy->_sound->isSoundPlaying(_clickSound)) {
+ }
+
+ if (_puzzleType != kPiano) {
+ if (_clickedSequence.size() < _correctSequence.size()) {
return;
}
- for (uint i = 0; i < _drawnElements.size(); ++i) {
- if (_drawnElements[i]) {
- undrawElement(i);
+ // Check the pressed sequence. If its length is above a certain number,
+ // clear it and start anew
+ if (_clickedSequence != _correctSequence) {
+ if (_puzzleType == kOrdering) {
+ uint maxNumPressed = 4;
+ if (g_nancy->getGameType() > kGameTypeVampire) {
+ if (_puzzleType == kOrderItems) {
+ maxNumPressed = _correctSequence.size() - 1;
+ } else {
+ maxNumPressed = _correctSequence.size() + 1;
+ }
+ }
+
+ if (_clickedSequence.size() > maxNumPressed) {
+ clearAllElements();
+ }
+ } 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;
}
-
- if (_clickedSequence.size() < _sequenceLength) {
+
+ 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;
+ }
+ }
+ } else {
+ // Piano puzzle checks only the last few elements
+ if (_clickedSequence.size() < _correctSequence.size()) {
return;
}
@@ -168,8 +251,8 @@ void OrderingPuzzle::execute() {
_clickedSequence.erase(&_clickedSequence[0], &_clickedSequence[_clickedSequence.size() - 6]);
}
- for (uint i = 0; i < _sequenceLength; ++i) {
- if (_clickedSequence[_clickedSequence.size() - _sequenceLength + i] != (int16)_correctSequence[i]) {
+ for (uint i = 0; i < _correctSequence.size(); ++i) {
+ if (_clickedSequence[_clickedSequence.size() - _correctSequence.size() + i] != (int16)_correctSequence[i]) {
return;
}
}
@@ -184,6 +267,7 @@ void OrderingPuzzle::execute() {
break;
}
+ g_nancy->_sound->loadSound(_solveSound);
g_nancy->_sound->playSound(_solveSound);
_solveState = kWaitForSound;
break;
@@ -194,13 +278,15 @@ void OrderingPuzzle::execute() {
break;
}
+
break;
case kActionTrigger:
if (g_nancy->getGameType() == kGameTypeVampire) {
g_nancy->_sound->stopSound("BUOK");
} else {
- g_nancy->_sound->stopSound(_clickSound);
+ g_nancy->_sound->stopSound(_pushDownSound);
}
+
g_nancy->_sound->stopSound(_solveSound);
if (_solveState == kNotSolved) {
@@ -220,7 +306,7 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
}
bool canClick = true;
- if (_puzzleType == kPiano && g_nancy->_sound->isSoundPlaying(_clickSound)) {
+ if (_itemsStayDown && g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
canClick = false;
}
@@ -238,25 +324,36 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
- if (g_nancy->getGameType() == kGameTypeVampire) {
- g_nancy->_sound->playSound("BUOK");
- } else {
- if (_puzzleType == kPiano) {
- if (Common::isDigit(_clickSound.name.lastChar())) {
- _clickSound.name.deleteLastChar();
- }
+ 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;
+ }
+ }
- _clickSound.name.insertChar('0' + i, _clickSound.name.size());
- g_nancy->_sound->loadSound(_clickSound);
+ if (_puzzleType == kPiano) {
+ // Set the correct sound name for every piano key
+ if (Common::isDigit(_pushDownSound.name.lastChar())) {
+ _pushDownSound.name.deleteLastChar();
}
- g_nancy->_sound->playSound(_clickSound);
+ _pushDownSound.name.insertChar('0' + i, _pushDownSound.name.size());
+ g_nancy->_sound->loadSound(_pushDownSound);
}
-
+
if (_puzzleType == kOrdering) {
+ // Ordering puzzle allows 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 && _drawnElements[i] == true) {
- undrawElement(i);
+ if (_clickedSequence[j] == i && _downItems[i] == true) {
+ popUp(i);
if (_clickedSequence.back() == i) {
_clickedSequence.pop_back();
}
@@ -267,7 +364,7 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
}
_clickedSequence.push_back(i);
- drawElement(i);
+ pushDown(i);
}
return;
@@ -275,25 +372,73 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
}
}
-void OrderingPuzzle::drawElement(uint id) {
- _drawnElements[id] = true;
- Common::Point destPoint(_destRects[id].left - _screenPosition.left, _destRects[id].top - _screenPosition.top);
- _drawSurface.blitFrom(_image, _srcRects[id], destPoint);
- setVisible(true);
+Common::String OrderingPuzzle::getRecordTypeName() const {
+ switch (_puzzleType) {
+ case kPiano:
+ return "PianoPuzzle";
+ case kOrderItems:
+ return "OrderItemsPuzzle";
+ default:
+ return "OrderingPuzzle";
+ }
}
-void OrderingPuzzle::undrawElement(uint id) {
- _drawnElements[id] = false;
- Common::Rect bounds = _destRects[id];
- bounds.translate(-_screenPosition.left, -_screenPosition.top);
+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 (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);
+ }
- _drawSurface.fillRect(bounds, g_nancy->_graphicsManager->getTransColor());
_needsRedraw = true;
}
void OrderingPuzzle::clearAllElements() {
- _drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
- setVisible(false);
+ for (uint id = 0; id < _downItems.size(); ++id) {
+ popUp(id);
+ }
+
_clickedSequence.clear();
return;
}
diff --git a/engines/nancy/action/orderingpuzzle.h b/engines/nancy/action/orderingpuzzle.h
index 58991ef5467..4d88ee3c797 100644
--- a/engines/nancy/action/orderingpuzzle.h
+++ b/engines/nancy/action/orderingpuzzle.h
@@ -27,10 +27,13 @@
namespace Nancy {
namespace Action {
+// Implements three different action record types: OrderingPuzzle,
+// PianoPuzzle, and OrderItemsPuzzle. All three have the same goal:
+// click on a series of items in the same order (hence the name).
class OrderingPuzzle : public RenderActionRecord {
public:
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
- enum PuzzleType { kOrdering, kPiano };
+ enum PuzzleType { kOrdering, kPiano, kOrderItems };
OrderingPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
virtual ~OrderingPuzzle() {}
@@ -41,20 +44,32 @@ public:
void handleInput(NancyInput &input) override;
protected:
- Common::String getRecordTypeName() const override { return _puzzleType == kOrdering ? "OrderingPuzzle" : "PianoPuzzle"; }
+ Common::String getRecordTypeName() const override;
bool isViewportRelative() const override { return true; }
- void drawElement(uint id);
- void undrawElement(uint id);
+ void pushDown(uint id);
+ void setToSecondState(uint id);
+ void popUp(uint id);
void clearAllElements();
Common::String _imageName;
- Common::Array<Common::Rect> _srcRects;
+ bool _hasSecondState = false;
+ bool _itemsStayDown = true;
+ 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;
- uint16 _sequenceLength = 0;
- Common::Array<byte> _correctSequence;
- Nancy::SoundDescription _clickSound;
+ Common::Array<uint16> _correctSequence;
+
+ 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;
@@ -63,8 +78,9 @@ protected:
SolveState _solveState = kNotSolved;
Graphics::ManagedSurface _image;
- Common::Array<int16> _clickedSequence;
- Common::Array<bool> _drawnElements;
+ Common::Array<uint16> _clickedSequence;
+ Common::Array<bool> _downItems;
+ Common::Array<bool> _secondStateItems;
Time _solveSoundPlayTime;
PuzzleType _puzzleType;
Commit: 327922404272a459405e4947519e2a0de87628b2
https://github.com/scummvm/scummvm/commit/327922404272a459405e4947519e2a0de87628b2
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-03T18:37:48+03:00
Commit Message:
NANCY: Show load button immediately after saving
Changed paths:
engines/nancy/state/loadsave.cpp
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
index e930958617e..891eaa66853 100644
--- a/engines/nancy/state/loadsave.cpp
+++ b/engines/nancy/state/loadsave.cpp
@@ -463,6 +463,7 @@ void LoadSaveMenu::save() {
_enteringNewState = true;
}
+ _saveExists[_selectedSave] = true;
g_nancy->_hasJustSaved = true;
}
Commit: b2a3194aadbc6914d16b79097235cb2487e02059
https://github.com/scummvm/scummvm/commit/b2a3194aadbc6914d16b79097235cb2487e02059
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-03T18:37:48+03:00
Commit Message:
NANCY: Implement CollisionPuzzle
Implemented the record type responsible for the
grid puzzle where pieces slide in four directions until they
hit a wall or another piece.
Changed paths:
A engines/nancy/action/collisionpuzzle.cpp
A engines/nancy/action/collisionpuzzle.h
engines/nancy/action/arfactory.cpp
engines/nancy/module.mk
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 3843d549734..97db2fa573c 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -41,6 +41,7 @@
#include "engines/nancy/action/turningpuzzle.h"
#include "engines/nancy/action/tangrampuzzle.h"
#include "engines/nancy/action/safelockpuzzle.h"
+#include "engines/nancy/action/collisionpuzzle.h"
#include "engines/nancy/state/scene.h"
@@ -210,6 +211,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
return new TurningPuzzle();
case 210:
return new SafeLockPuzzle();
+ case 211:
+ return new CollisionPuzzle();
case 212:
return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kOrderItems);
default:
diff --git a/engines/nancy/action/collisionpuzzle.cpp b/engines/nancy/action/collisionpuzzle.cpp
new file mode 100644
index 00000000000..6415e89f7f4
--- /dev/null
+++ b/engines/nancy/action/collisionpuzzle.cpp
@@ -0,0 +1,503 @@
+/* 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/collisionpuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+void CollisionPuzzle::init() {
+ Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
+ _drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
+ _drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+ setTransparent(true);
+ setVisible(true);
+ moveTo(screenBounds);
+
+ g_nancy->_resource->loadImage(_imageName, _image);
+ _image.setTransparentColor(_drawSurface.getTransparentColor());
+
+ _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);
+ }
+
+ drawGrid();
+ registerGraphics();
+}
+
+void CollisionPuzzle::registerGraphics() {
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ _pieces[i].registerGraphics();
+ }
+
+ RenderActionRecord::registerGraphics();
+}
+
+void CollisionPuzzle::updateGraphics() {
+ if (_state == kRun && _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 (_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 width = stream.readUint16LE();
+ uint16 height = stream.readUint16LE();
+ uint16 numPieces = 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(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);
+ stream.skip((5 - numPieces) * 16);
+
+ readRectArray(stream, _homeSrcs, numPieces);
+ stream.skip((5 - numPieces) * 16);
+
+ readRect(stream, _verticalWallSrc);
+ readRect(stream, _horizontalWallSrc);
+ readRect(stream, _blockSrc);
+
+ _gridPos.x = stream.readUint32LE();
+ _gridPos.y = stream.readUint32LE();
+
+ _lineWidth = stream.readUint16LE();
+ _framesPerMove = stream.readUint16LE();
+
+ stream.skip(3);
+
+ _moveSound.readNormal(stream);
+ _homeSound.readNormal(stream);
+ _wallHitSound.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);
+ _state = kRun;
+ // fall through
+ case kRun :
+ if (_currentlyAnimating != -1) {
+ return;
+ }
+
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ if (_grid[_pieces[i]._gridPos.y][_pieces[i]._gridPos.x] != i + 1) {
+ 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 {
+ _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 };
+ }
+
+ int lastPos = inc > 0 ? (horizontal ? (int)_grid[0].size() : (int)_grid.size()) : -1;
+ for (int i = (horizontal ? newPos.x : newPos.y) + inc; i != lastPos; i += inc) {
+ // First, check if other pieces would block
+ Common::Point comparePos = newPos;
+ if (horizontal) {
+ comparePos.x = i;
+ } else {
+ comparePos.y = i;
+ }
+
+ for (uint j = 0; j < _pieces.size(); ++j) {
+ if (pieceID == j) {
+ continue;
+ }
+
+ if (_pieces[j]._gridPos == comparePos) {
+ done = true;
+ break;
+ }
+ }
+
+ if (done) {
+ break;
+ }
+
+ // 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;
+ }
+ }
+
+ return newPos;
+}
+
+Common::Rect CollisionPuzzle::getScreenPosition(Common::Point gridPos) {
+ Common::Rect dest = _pieces[0]._drawSurface.getBounds();
+ dest.right -= 1;
+ dest.bottom -= 1;
+ 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 (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+ // For some reason, this puzzle uses the backwards arrow for exit
+ g_nancy->_cursorManager->setCursorType(CursorManager::kMoveBackward);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _state = kActionTrigger;
+ }
+ return;
+ }
+
+ Common::Rect left, right, up, down;
+ left.setWidth(10);
+ left.setHeight(_pieceSrcs[0].height() - 20);
+ left.moveTo(0, 10);
+ right = left;
+ right.translate(_pieceSrcs[0].width() - 10, 0);
+
+ up.setHeight(10);
+ up.setWidth(_pieceSrcs[0].width() - 20);
+ up.moveTo(10, 0);
+ down = up;
+ down.translate(0, _pieceSrcs[0].width() - 10);
+
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ Common::Rect gridPos = getScreenPosition(_pieces[i]._gridPos);
+
+ left.translate(gridPos.left, gridPos.top);
+ right.translate(gridPos.left, gridPos.top);
+ up.translate(gridPos.left, gridPos.top);
+ down.translate(gridPos.left, gridPos.top);
+
+ Common::Point checkPos = movePiece(i, kWallLeft);
+ if (checkPos != _pieces[i]._gridPos) {
+ if (NancySceneState.getViewport().convertViewportToScreen(left).contains(input.mousePos)) {
+ g_nancy->_cursorManager->setCursorType(CursorManager::kTurnLeft);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _lastPosition = _pieces[i]._gridPos;
+ _pieces[i]._gridPos = checkPos;
+ _currentlyAnimating = i;
+ g_nancy->_sound->playSound(_moveSound);
+ }
+
+ return;
+ }
+ }
+
+ checkPos = movePiece(i, kWallRight);
+ if (checkPos != _pieces[i]._gridPos) {
+ if (NancySceneState.getViewport().convertViewportToScreen(right).contains(input.mousePos)) {
+ g_nancy->_cursorManager->setCursorType(CursorManager::kTurnRight);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _lastPosition = _pieces[i]._gridPos;
+ _pieces[i]._gridPos = checkPos;
+ _currentlyAnimating = i;
+ g_nancy->_sound->playSound(_moveSound);
+ }
+
+ return;
+ }
+ }
+
+ checkPos = movePiece(i, kWallUp);
+ if (checkPos != _pieces[i]._gridPos) {
+ if (NancySceneState.getViewport().convertViewportToScreen(up).contains(input.mousePos)) {
+ g_nancy->_cursorManager->setCursorType(CursorManager::kMoveUp);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _lastPosition = _pieces[i]._gridPos;
+ _pieces[i]._gridPos = checkPos;
+ _currentlyAnimating = i;
+ g_nancy->_sound->playSound(_moveSound);
+ }
+
+ return;
+ }
+ }
+
+ checkPos = movePiece(i, kWallDown);
+ if (checkPos != _pieces[i]._gridPos) {
+ if (NancySceneState.getViewport().convertViewportToScreen(down).contains(input.mousePos)) {
+ g_nancy->_cursorManager->setCursorType(CursorManager::kMoveDown);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _lastPosition = _pieces[i]._gridPos;
+ _pieces[i]._gridPos = checkPos;
+ _currentlyAnimating = i;
+ g_nancy->_sound->playSound(_moveSound);
+ }
+
+ return;
+ }
+ }
+
+ left.translate(-gridPos.left, -gridPos.top);
+ right.translate(-gridPos.left, -gridPos.top);
+ up.translate(-gridPos.left, -gridPos.top);
+ down.translate(-gridPos.left, -gridPos.top);
+ }
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/collisionpuzzle.h b/engines/nancy/action/collisionpuzzle.h
new file mode 100644
index 00000000000..5bbc3505de4
--- /dev/null
+++ b/engines/nancy/action/collisionpuzzle.h
@@ -0,0 +1,107 @@
+/* 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 CollisionPuzzle : public RenderActionRecord {
+public:
+ CollisionPuzzle() : RenderActionRecord(7) {}
+ 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;
+
+ protected:
+ bool isViewportRelative() const override { return true; }
+ };
+
+ Common::String getRecordTypeName() const override { return "CollisionPuzzle"; };
+ bool isViewportRelative() const override { return true; }
+
+ Common::Point movePiece(uint pieceID, WallType direction);
+ Common::Rect getScreenPosition(Common::Point gridPos);
+ void drawGrid();
+
+ Common::String _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 _gridPos;
+
+ uint16 _lineWidth;
+ uint16 _framesPerMove;
+
+ SoundDescription _moveSound;
+ SoundDescription _homeSound;
+ SoundDescription _wallHitSound;
+
+ SceneChangeWithFlag _solveScene;
+ uint16 _solveSoundDelay;
+ 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;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_COLLISIONPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index c89d91a64e2..c060ec07165 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
action/actionrecord.o \
action/arfactory.o \
action/bombpuzzle.o \
+ action/collisionpuzzle.o \
action/conversation.o \
action/leverpuzzle.o \
action/orderingpuzzle.o \
More information about the Scummvm-git-logs
mailing list