[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