[Scummvm-git-logs] scummvm master -> 6f0e74c620a53c90bc0a9535f761dc7e7b77f698

fracturehill noreply at scummvm.org
Tue Sep 12 16:44:01 UTC 2023


This automated email contains information about 10 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
4cb1ab3f9b NANCY: Improve readRectArray() utility function
99783b5408 NANCY: Implement KeypadPuzzle
09ef41280d NANCY: Fix Overlay reading
2d1dd3b731 NANCY: Implement TileMovePuzzle
da0238d91b NANCY: Use correct exit cursor in nancy4 puzzles
52b66621fa NANCY: Skip drawing first frame after loading save
7f8d6a47d3 NANCY: Avoid flashing cursor when changing scene
aecaf3591f NANCY: Reorganize ActionRecords
2ee8b2cea6 NANCY: Implement MazeChasePuzzle
6f0e74c620 NANCY: Play correct sfx when getting/losing item


Commit: 4cb1ab3f9b79473f410f4c8af4eaa9d6c84178fa
    https://github.com/scummvm/scummvm/commit/4cb1ab3f9b79473f410f4c8af4eaa9d6c84178fa
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:29+03:00

Commit Message:
NANCY: Improve readRectArray() utility function

The data in a lot of action record types has a fixed number
of rects in an array, only some of which is actually filled.
readRectArray() has now been modified to take in an
additional parameter indicating the max number of rects
that could be present in the passed stream, so it can
automatically skip to the end, reducing boilerplate code
in calling functions. The function now also appends rects
to the provided array, rather than overwrite them. Also, all
readRect() variants now do not extend rects by 1 pixel when
they are empty, ensuring calls to isEmpty() succeed.

Changed paths:
    engines/nancy/action/collisionpuzzle.cpp
    engines/nancy/action/orderingpuzzle.cpp
    engines/nancy/action/overridelockpuzzle.cpp
    engines/nancy/action/rippedletterpuzzle.cpp
    engines/nancy/action/sliderpuzzle.cpp
    engines/nancy/action/towerpuzzle.cpp
    engines/nancy/action/turningpuzzle.cpp
    engines/nancy/enginedata.cpp
    engines/nancy/util.cpp
    engines/nancy/util.h


diff --git a/engines/nancy/action/collisionpuzzle.cpp b/engines/nancy/action/collisionpuzzle.cpp
index 6415e89f7f4..c33e144adcc 100644
--- a/engines/nancy/action/collisionpuzzle.cpp
+++ b/engines/nancy/action/collisionpuzzle.cpp
@@ -133,11 +133,8 @@ void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
 	}
 	stream.skip((5 - numPieces) * 4);
 
-	readRectArray(stream, _pieceSrcs, numPieces);
-	stream.skip((5 - numPieces) * 16);
-
-	readRectArray(stream, _homeSrcs, numPieces);
-	stream.skip((5 - numPieces) * 16);
+	readRectArray(stream, _pieceSrcs, numPieces, 5);
+	readRectArray(stream, _homeSrcs, numPieces, 5);
 	
 	readRect(stream, _verticalWallSrc);
 	readRect(stream, _horizontalWallSrc);
diff --git a/engines/nancy/action/orderingpuzzle.cpp b/engines/nancy/action/orderingpuzzle.cpp
index f29c33fc8cf..6dad62f3171 100644
--- a/engines/nancy/action/orderingpuzzle.cpp
+++ b/engines/nancy/action/orderingpuzzle.cpp
@@ -86,24 +86,20 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 		_itemsStayDown = false;
 	}
 
-	readRectArray(stream, _down1Rects, numElements);
-	ser.skip(16 * (15 - numElements), kGameTypeNancy1);
+	readRectArray(ser, _down1Rects, numElements, numElements, kGameTypeVampire, kGameTypeVampire);
+	readRectArray(ser, _down1Rects, numElements, 15, kGameTypeNancy1);
 
 	if (isOrderItems) {
-		readRectArray(stream, _up2Rects, numElements);
-		ser.skip(16 * (15 - numElements));
-
-		readRectArray(stream, _down2Rects, numElements);
-		ser.skip(16 * (15 - numElements));
+		readRectArray(stream, _up2Rects, numElements, 15);
+		readRectArray(stream, _down2Rects, numElements, 15);
 	}
 
-	readRectArray(stream, _destRects, numElements);
-	ser.skip(16 * (15 - numElements), kGameTypeNancy1);
+	readRectArray(ser, _destRects, numElements, numElements, kGameTypeVampire, kGameTypeVampire);
+	readRectArray(ser, _destRects, numElements, 15, kGameTypeNancy1);
 
 	_hotspots.resize(numElements);
 	if (isPiano) {
-		readRectArray(stream, _hotspots, numElements);
-		ser.skip(16 * (15 - numElements));
+		readRectArray(stream, _hotspots, numElements, 15);
 	} else {
 		_hotspots = _destRects;
 	}
diff --git a/engines/nancy/action/overridelockpuzzle.cpp b/engines/nancy/action/overridelockpuzzle.cpp
index 31ee0edc918..f37ac8ad283 100644
--- a/engines/nancy/action/overridelockpuzzle.cpp
+++ b/engines/nancy/action/overridelockpuzzle.cpp
@@ -56,16 +56,11 @@ void OverrideLockPuzzle::readData(Common::SeekableReadStream &stream) {
 	_popButtons = stream.readByte();
 	_randomizeLights = stream.readByte();
 
-	readRectArray(stream, _buttonSrcs, num);
-	stream.skip((10 - num) * 16);
-	readRectArray(stream, _buttonDests, num);
-	stream.skip((10 - num) * 16);
-	readRectArray(stream, _hotspots, num);
-	stream.skip((10 - num) * 16);
-	readRectArray(stream, _lightSrcs, num);
-	stream.skip((10 - num) * 16);
-	readRectArray(stream, _lightDests, num);
-	stream.skip((10 - num) * 16);
+	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);
diff --git a/engines/nancy/action/rippedletterpuzzle.cpp b/engines/nancy/action/rippedletterpuzzle.cpp
index 8ecd1a420e7..5d502ec47f2 100644
--- a/engines/nancy/action/rippedletterpuzzle.cpp
+++ b/engines/nancy/action/rippedletterpuzzle.cpp
@@ -69,21 +69,13 @@ void RippedLetterPuzzle::readData(Common::SeekableReadStream &stream) {
 		height = stream.readByte();
 	}
 
-	_srcRects.resize(width * height);
-	_destRects.resize(width * height);
 	for (uint i = 0; i < height; ++i) {
-		for (uint j = 0; j < width; ++j) {
-			readRect(stream, _srcRects[i * width + j]);
-		}
-		stream.skip((6 - width) * 16);
+		readRectArray(stream, _srcRects, width, 6);
 	}
 	stream.skip((4 - height) * 6 * 16);
 
 	for (uint i = 0; i < height; ++i) {
-		for (uint j = 0; j < width; ++j) {
-			readRect(stream, _destRects[i * width + j]);
-		}
-		stream.skip((6 - width) * 16);
+		readRectArray(stream, _destRects, width, 6);
 	}
 	stream.skip((4 - height) * 6 * 16);
 
diff --git a/engines/nancy/action/sliderpuzzle.cpp b/engines/nancy/action/sliderpuzzle.cpp
index 49845bd637b..3a74fc6a4fc 100644
--- a/engines/nancy/action/sliderpuzzle.cpp
+++ b/engines/nancy/action/sliderpuzzle.cpp
@@ -55,41 +55,25 @@ void SliderPuzzle::readData(Common::SeekableReadStream &stream) {
 	_width = stream.readUint16LE();
 	_height = stream.readUint16LE();
 
-	_srcRects.reserve(_height);
+	_srcRects.resize(_height);
 	for (uint y = 0; y < _height; ++y) {
-		_srcRects.push_back(Common::Array<Common::Rect>());
-		_srcRects.back().reserve(_width);
-
-		for (uint x = 0; x < _width; ++x) {
-			_srcRects.back().push_back(Common::Rect());
-			readRect(stream, _srcRects.back().back());
-		}
-
-		stream.skip((6 - _width) * 16);
+		readRectArray(stream, _srcRects[y], _width, 6);
 	}
-
 	stream.skip((6 - _height) * 6 * 16);
 
-	_destRects.reserve(_height);
+	_destRects.resize(_height);
 	for (uint y = 0; y < _height; ++y) {
-		_destRects.push_back(Common::Array<Common::Rect>());
-		_destRects.back().reserve(_width);
+		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) {
-			_destRects.back().push_back(Common::Rect());
-			readRect(stream, _destRects.back().back());
-
-			if (x == 0 && y == 0) {
-				_screenPosition = _destRects.back().back();
-			} else {
-				_screenPosition.extend(_destRects.back().back());
-			}
+			_screenPosition.extend(_destRects[y][x]);
 		}
-		stream.skip((6 - _width) * 16);
 	}
 
-	stream.skip((6 - _height) * 6 * 16);
-
 	_correctTileOrder.reserve(_height);
 	for (uint y = 0; y < _height; ++y) {
 		_correctTileOrder.push_back(Common::Array<int16>());
diff --git a/engines/nancy/action/towerpuzzle.cpp b/engines/nancy/action/towerpuzzle.cpp
index 2f2ef2bfe4e..a4dff1bcc7e 100644
--- a/engines/nancy/action/towerpuzzle.cpp
+++ b/engines/nancy/action/towerpuzzle.cpp
@@ -76,8 +76,7 @@ void TowerPuzzle::readData(Common::SeekableReadStream &stream) {
 			// 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);
-			stream.skip((6 - ringID - 1) * 16);
+			readRectArray(stream, _destRects[ringID][poleID], ringID + 1, 6);
 		}
 	}
 
diff --git a/engines/nancy/action/turningpuzzle.cpp b/engines/nancy/action/turningpuzzle.cpp
index ffb6d0ca151..e2889ad232e 100644
--- a/engines/nancy/action/turningpuzzle.cpp
+++ b/engines/nancy/action/turningpuzzle.cpp
@@ -139,11 +139,8 @@ void TurningPuzzle::readData(Common::SeekableReadStream &stream) {
 	}
 	stream.skip((16 - numSpindles) * 2);
 
-	readRectArray(stream, _destRects, numSpindles);
-	stream.skip((16 - numSpindles) * 16);
-
-	readRectArray(stream, _hotspots, numSpindles);
-	stream.skip((16 - numSpindles) * 16);
+	readRectArray(stream, _destRects, numSpindles, 16);
+	readRectArray(stream, _hotspots, numSpindles, 16);
 
 	_separateRows = stream.readByte();
 
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 8f02f021b84..031355fdcb6 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -103,8 +103,8 @@ INV::INV(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	s.syncAsUint16LE(scrollbarDefaultPos.y);
 	s.syncAsUint16LE(scrollbarMaxScroll);
 
-	readRectArray(s, ornamentSrcs, 6, kGameTypeVampire, kGameTypeNancy1);
-	readRectArray(s, ornamentDests, 6, kGameTypeVampire, kGameTypeNancy1);
+	readRectArray(s, ornamentSrcs, 6, 6, kGameTypeVampire, kGameTypeNancy1);
+	readRectArray(s, ornamentDests, 6, 6, kGameTypeVampire, kGameTypeNancy1);
 
 	uint numFrames = g_nancy->getStaticData().numCurtainAnimationFrames;
 
@@ -247,7 +247,7 @@ MAP::MAP(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	s.skip(0x20);
 
 	s.syncAsUint16LE(globeFrameTime, kGameTypeVampire, kGameTypeVampire);
-	readRectArray(s, globeSrcs, 8, kGameTypeVampire, kGameTypeVampire);
+	readRectArray(s, globeSrcs, 8, 8, kGameTypeVampire, kGameTypeVampire);
 	readRect(s, globeDest, kGameTypeVampire, kGameTypeVampire);
 
 	s.skip(2, kGameTypeNancy1);
@@ -337,38 +337,21 @@ CRED::CRED(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 MENU::MENU(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	readFilename(*chunkStream, _imageName);
-	chunkStream->skip(22);
-
-	uint numOptions = 8;
+	Common::Serializer ser(chunkStream, nullptr);
+	ser.setVersion(g_nancy->getGameType());
+	readFilename(ser, _imageName);
 
-	_buttonDests.resize(numOptions);
-	_buttonDownSrcs.resize(numOptions);
+	ser.skip(22);
 
-	if (g_nancy->getGameType() <= kGameTypeNancy1) {
-		for (uint i = 0; i < numOptions; ++i) {
-			Common::Rect &rect = _buttonDests[i];
-			rect.left = chunkStream->readSint16LE();
-			rect.top = chunkStream->readSint16LE();
-			rect.right = chunkStream->readSint16LE();
-			rect.bottom = chunkStream->readSint16LE();
-		}
+	uint numOptions = 8;
 
-		for (uint i = 0; i < numOptions; ++i) {
-			Common::Rect &rect = _buttonDownSrcs[i];
-			rect.left = chunkStream->readSint16LE();
-			rect.top = chunkStream->readSint16LE();
-			rect.right = chunkStream->readSint16LE();
-			rect.bottom = chunkStream->readSint16LE();
-		}
-	} else {
-		_buttonHighlightSrcs.resize(numOptions);
+	readRectArray16(ser, _buttonDests, numOptions, numOptions, kGameTypeVampire, kGameTypeNancy1);
+	readRectArray16(ser, _buttonDownSrcs, numOptions, numOptions, kGameTypeVampire, kGameTypeNancy1);
 
-		readRectArray(*chunkStream, _buttonDests, numOptions);
-		readRectArray(*chunkStream, _buttonDownSrcs, numOptions);
-		readRectArray(*chunkStream, _buttonDisabledSrcs, numOptions);
-		readRectArray(*chunkStream, _buttonHighlightSrcs, numOptions);
-	}
+	readRectArray(ser, _buttonDests, numOptions, numOptions, kGameTypeNancy2);
+	readRectArray(ser, _buttonDownSrcs, numOptions, numOptions, kGameTypeNancy2);
+	readRectArray(ser, _buttonDisabledSrcs, numOptions, numOptions, kGameTypeNancy2);
+	readRectArray(ser, _buttonHighlightSrcs, numOptions, numOptions, kGameTypeNancy2);
 }
 
 SET::SET(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
@@ -514,21 +497,21 @@ CLOK::CLOK(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	uint numFrames = s.getVersion() == kGameTypeVampire? 8 : 7;
 
 	readRectArray(s, animSrcs, numFrames);
-	readRectArray(s, animDests, numFrames, kGameTypeNancy2);
+	readRectArray(s, animDests, numFrames, numFrames, kGameTypeNancy2);
 
 	readRect(s, staticImageSrc, kGameTypeNancy2);
 	readRect(s, staticImageDest, kGameTypeNancy2);
 
 	readRectArray(s, hoursHandSrcs, 12);
-	readRectArray(s, hoursHandDests, 12, kGameTypeNancy2);
+	readRectArray(s, hoursHandDests, 12, 12, kGameTypeNancy2);
 
 	readRectArray(s, minutesHandSrcs, 4);
-	readRectArray(s, minutesHandDests, 4, kGameTypeNancy2);
+	readRectArray(s, minutesHandDests, 4, 4, kGameTypeNancy2);
 
 	readRect(s, screenPosition, kGameTypeVampire, kGameTypeVampire);
 
-	readRectArray(s, hoursHandDests, 12, kGameTypeVampire, kGameTypeVampire);
-	readRectArray(s, minutesHandDests, 4, kGameTypeVampire, kGameTypeVampire);
+	readRectArray(s, hoursHandDests, 12, 12, kGameTypeVampire, kGameTypeVampire);
+	readRectArray(s, minutesHandDests, 4, 4, kGameTypeVampire, kGameTypeVampire);
 
 	readRect(s, staticImageSrc, kGameTypeVampire, kGameTypeVampire);
 	readRect(s, staticImageDest, kGameTypeVampire, kGameTypeVampire);
@@ -539,8 +522,8 @@ CLOK::CLOK(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	s.skip(2, kGameTypeNancy5);
 	s.syncAsUint32LE(nancy5CountdownTime, kGameTypeNancy5);
 	s.skip(2, kGameTypeNancy5);
-	readRectArray(s, nancy5DaySrcs, 3, kGameTypeNancy5);
-	readRectArray(s, nancy5CountdownSrcs, 13, kGameTypeNancy5);
+	readRectArray(s, nancy5DaySrcs, 3, 3, kGameTypeNancy5);
+	readRectArray(s, nancy5CountdownSrcs, 13, 13, kGameTypeNancy5);
 }
 
 SPEC::SPEC(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
diff --git a/engines/nancy/util.cpp b/engines/nancy/util.cpp
index 04477a9bc4f..a2bd970e7eb 100644
--- a/engines/nancy/util.cpp
+++ b/engines/nancy/util.cpp
@@ -31,7 +31,7 @@ void readRect(Common::SeekableReadStream &stream, Common::Rect &inRect) {
 	inRect.bottom = stream.readSint32LE();
 
 	// TVD's rects are non-inclusive
-	if (g_nancy->getGameType() > kGameTypeVampire) {
+	if (g_nancy->getGameType() > kGameTypeVampire && !inRect.isEmpty()) {
 		++inRect.right;
 		++inRect.bottom;
 	}
@@ -46,36 +46,52 @@ void readRect(Common::Serializer &stream, Common::Rect &inRect, Common::Serializ
 		stream.syncAsSint32LE(inRect.bottom);
 
 		// TVD's rects are non-inclusive
-		if (version > kGameTypeVampire) {
+		if (version > kGameTypeVampire && !inRect.isEmpty()) {
 			++inRect.right;
 			++inRect.bottom;
 		}
 	}
 }
 
-void readRectArray(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num) {
-	inArray.resize(num);
-	for (Common::Rect &rect : inArray) {
-		readRect(stream, rect);
+void readRectArray(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum) {
+	uint startSize = inArray.size();
+	inArray.resize(num + startSize);
+
+	for (Common::Rect *rect = &inArray[startSize]; rect != inArray.end(); ++rect) {
+		readRect(stream, *rect);
 	}
+
+	if (totalNum == 0) {
+		totalNum = num;
+	}
+
+	stream.skip((totalNum - num) * 16);
 }
 
-void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
+void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
 	Common::Serializer::Version version = stream.getVersion();
 	if (version >= minVersion && version <= maxVersion) {
-		inArray.resize(num);
-		for (Common::Rect &rect : inArray) {
-			stream.syncAsSint32LE(rect.left);
-			stream.syncAsSint32LE(rect.top);
-			stream.syncAsSint32LE(rect.right);
-			stream.syncAsSint32LE(rect.bottom);
+		uint startSize = inArray.size();
+		inArray.resize(num + startSize);
+
+		for (Common::Rect *rect = &inArray[startSize]; rect != inArray.end(); ++rect) {
+			stream.syncAsSint32LE(rect->left);
+			stream.syncAsSint32LE(rect->top);
+			stream.syncAsSint32LE(rect->right);
+			stream.syncAsSint32LE(rect->bottom);
 
 			// TVD's rects are non-inclusive
-			if (version > kGameTypeVampire) {
-				++rect.right;
-				++rect.bottom;
+			if (version > kGameTypeVampire && !rect->isEmpty()) {
+				++rect->right;
+				++rect->bottom;
 			}
 		}
+
+		if (totalNum == 0) {
+			totalNum = num;
+		}
+
+		stream.skip((totalNum - num) * 16);
 	}
 }
 
@@ -86,7 +102,7 @@ void readRect16(Common::SeekableReadStream &stream, Common::Rect &inRect) {
 	inRect.bottom = stream.readSint16LE();
 
 	// TVD's rects are non-inclusive
-	if (g_nancy->getGameType() > kGameTypeVampire) {
+	if (g_nancy->getGameType() > kGameTypeVampire && !inRect.isEmpty()) {
 		++inRect.right;
 		++inRect.bottom;
 	}
@@ -101,36 +117,52 @@ void readRect16(Common::Serializer &stream, Common::Rect &inRect, Common::Serial
 		stream.syncAsSint16LE(inRect.bottom);
 
 		// TVD's rects are non-inclusive
-		if (version > kGameTypeVampire) {
+		if (version > kGameTypeVampire && !inRect.isEmpty()) {
 			++inRect.right;
 			++inRect.bottom;
 		}
 	}
 }
 
-void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num) {
-	inArray.resize(num);
-	for (Common::Rect &rect : inArray) {
-		readRect16(stream, rect);
+void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum) {
+	uint startSize = inArray.size();
+	inArray.resize(num + startSize);
+
+	for (Common::Rect *rect = &inArray[startSize]; rect != inArray.end(); ++rect) {
+		readRect16(stream, *rect);
 	}
+
+	if (totalNum == 0) {
+		totalNum = num;
+	}
+
+	stream.skip((totalNum - num) * 8);
 }
 
-void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
+void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum, Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
 	Common::Serializer::Version version = stream.getVersion();
 	if (version >= minVersion && version <= maxVersion) {
-		inArray.resize(num);
-		for (Common::Rect &rect : inArray) {
-			stream.syncAsSint16LE(rect.left);
-			stream.syncAsSint16LE(rect.top);
-			stream.syncAsSint16LE(rect.right);
-			stream.syncAsSint16LE(rect.bottom);
+		uint startSize = inArray.size();
+		inArray.resize(num + startSize);
+
+		for (Common::Rect *rect = &inArray[startSize]; rect != inArray.end(); ++rect) {
+			stream.syncAsSint16LE(rect->left);
+			stream.syncAsSint16LE(rect->top);
+			stream.syncAsSint16LE(rect->right);
+			stream.syncAsSint16LE(rect->bottom);
 
 			// TVD's rects are non-inclusive
-			if (version > kGameTypeVampire) {
-				++rect.right;
-				++rect.bottom;
+			if (version > kGameTypeVampire && !rect->isEmpty()) {
+				++rect->right;
+				++rect->bottom;
 			}
 		}
+
+		if (totalNum == 0) {
+			totalNum = num;
+		}
+
+		stream.skip((totalNum - num) * 8);
 	}
 }
 
diff --git a/engines/nancy/util.h b/engines/nancy/util.h
index 61eabe711ba..5aa79ccb250 100644
--- a/engines/nancy/util.h
+++ b/engines/nancy/util.h
@@ -28,13 +28,13 @@ namespace Nancy {
 
 void readRect(Common::SeekableReadStream &stream, Common::Rect &inRect);
 void readRect(Common::Serializer &stream, Common::Rect &inRect, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
-void readRectArray(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num);
-void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
+void readRectArray(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum = 0);
+void readRectArray(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum = 0, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
 
 void readRect16(Common::SeekableReadStream &stream, Common::Rect &inRect);
 void readRect16(Common::Serializer &stream, Common::Rect &inRect, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
-void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num);
-void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
+void readRectArray16(Common::SeekableReadStream &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum = 0);
+void readRectArray16(Common::Serializer &stream, Common::Array<Common::Rect> &inArray, uint num, uint totalNum = 0, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
 
 void readFilename(Common::SeekableReadStream &stream, Common::String &inString);
 void readFilename(Common::Serializer &stream, Common::String &inString, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);


Commit: 99783b5408a197dcb4c71a40336969b5241ba10d
    https://github.com/scummvm/scummvm/commit/99783b5408a197dcb4c71a40336969b5241ba10d
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:29+03:00

Commit Message:
NANCY: Implement KeypadPuzzle

Implemented the KeypadPuzzle action record type, which
is yet another variation on OrderingPuzzle, this time with
more buttons and the addition of a button for manually
checking whether the sequence is correct.

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 97db2fa573c..6039abd2f5e 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -215,6 +215,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new CollisionPuzzle();
 	case 212:
 		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kOrderItems);
+	case 214:
+		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kKeypad);
 	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 6dad62f3171..aa1d57f79cb 100644
--- a/engines/nancy/action/orderingpuzzle.cpp
+++ b/engines/nancy/action/orderingpuzzle.cpp
@@ -48,6 +48,10 @@ void OrderingPuzzle::init() {
 		_screenPosition.extend(_overlayDests[i]);
 	}
 
+	if (!_checkButtonDest.isEmpty()) {
+		_screenPosition.extend(_checkButtonDest);
+	}
+
 	g_nancy->_resource->loadImage(_imageName, _image);
 	_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphicsManager->getInputPixelFormat());
 
@@ -72,34 +76,45 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 	ser.setVersion(g_nancy->getGameType());
 
 	uint16 numElements;
+	uint16 maxNumElements = 15;
 	if (ser.getVersion() == kGameTypeVampire) {
 		// Hardcoded in The Vampire Diaries
-		numElements = 5;
+		numElements = maxNumElements = 5;
 	} else {
 		ser.syncAsUint16LE(numElements);
 	}
 
-	if (isOrderItems) {
+	switch (_puzzleType) {
+	case kOrderItems :
 		ser.syncAsByte(_hasSecondState);
 		ser.syncAsByte(_itemsStayDown);
-	} else if (isPiano) {
+		break;
+	case kPiano :
 		_itemsStayDown = false;
+		break;
+	case kKeypad :
+		ser.syncAsByte(_itemsStayDown);
+		ser.syncAsByte(_needButtonToCheckSuccess);
+		readRect(ser, _checkButtonSrc);
+		readRect(ser, _checkButtonDest);
+		maxNumElements = 30;
+		break;
+	default:
+		break;
 	}
 
-	readRectArray(ser, _down1Rects, numElements, numElements, kGameTypeVampire, kGameTypeVampire);
-	readRectArray(ser, _down1Rects, numElements, 15, kGameTypeNancy1);
+	readRectArray(ser, _down1Rects, numElements, maxNumElements);
 
 	if (isOrderItems) {
-		readRectArray(stream, _up2Rects, numElements, 15);
-		readRectArray(stream, _down2Rects, numElements, 15);
+		readRectArray(stream, _up2Rects, numElements, maxNumElements);
+		readRectArray(stream, _down2Rects, numElements, maxNumElements);
 	}
 
-	readRectArray(ser, _destRects, numElements, numElements, kGameTypeVampire, kGameTypeVampire);
-	readRectArray(ser, _destRects, numElements, 15, kGameTypeNancy1);
+	readRectArray(ser, _destRects, numElements, maxNumElements);
 
 	_hotspots.resize(numElements);
 	if (isPiano) {
-		readRectArray(stream, _hotspots, numElements, 15);
+		readRectArray(stream, _hotspots, numElements, maxNumElements);
 	} else {
 		_hotspots = _destRects;
 	}
@@ -110,6 +125,8 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 	_correctSequence.resize(sequenceLength);
 	for (uint i = 0; i < sequenceLength; ++i) {
 		switch (_puzzleType) {
+		case kKeypad :
+			// fall through
 		case kOrdering:
 			ser.syncAsByte(_correctSequence[i]);
 			break;
@@ -123,7 +140,7 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 			break;
 		}
 	}
-	ser.skip((15 - sequenceLength) * (_puzzleType == kOrdering ? 1 : 2), kGameTypeNancy1);
+	ser.skip((maxNumElements - sequenceLength) * ((_puzzleType == kOrdering || _puzzleType == kKeypad) ? 1 : 2), kGameTypeNancy1);
 
 	if (isOrderItems) {
 		uint numOverlays = 0;
@@ -169,7 +186,7 @@ void OrderingPuzzle::execute() {
 		// fall through
 	case kRun:
 		switch (_solveState) {
-		case kNotSolved:
+		case kNotSolved: {
 			if (!_itemsStayDown) {
 				// Clear the pushed item
 				if (g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
@@ -182,60 +199,48 @@ void OrderingPuzzle::execute() {
 					}
 				}
 			}
+			
+			bool solved = true;
 
 			if (_puzzleType != kPiano) {
-				if (_clickedSequence.size() < _correctSequence.size()) {
-					return;
-				}
-
-				// 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() >= _correctSequence.size()) {
+					// Check the pressed sequence. If its length is above a certain number,
+					// clear it and start anew
+					if (_clickedSequence != _correctSequence) {
+						if (_puzzleType != kOrderItems) {
+							uint maxNumPressed = 4;
+							if (g_nancy->getGameType() > kGameTypeVampire) {
+								if (_puzzleType == kKeypad) {
+									maxNumPressed = _correctSequence.size();
+								} 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;
+							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;
+									}
 								}
 							}
 						}
-					}
 
-					
-					return;
-				}
-
-				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;
+						solved = false;
 					}
-				}				
+				} else {
+					solved = false;
+				}						
 			} else {
 				// Piano puzzle checks only the last few elements
 				if (_clickedSequence.size() < _correctSequence.size()) {
@@ -244,7 +249,7 @@ void OrderingPuzzle::execute() {
 
 				// Arbitrary number
 				if (_clickedSequence.size() > 30) {
-					_clickedSequence.erase(&_clickedSequence[0], &_clickedSequence[_clickedSequence.size() - 6]);
+					_clickedSequence.erase(&_clickedSequence[0], &_clickedSequence[_clickedSequence.size() - _correctSequence.size()]);
 				}
 
 				for (uint i = 0; i < _correctSequence.size(); ++i) {
@@ -254,9 +259,46 @@ void OrderingPuzzle::execute() {
 				}
 			}
 
-			NancySceneState.setEventFlag(_solveExitScene._flag);
+			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) {
@@ -315,6 +357,20 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
 		return;
 	}
 
+	if (_needButtonToCheckSuccess && NancySceneState.getViewport().convertViewportToScreen(_checkButtonDest).contains(input.mousePos)) {
+		g_nancy->_cursorManager->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)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
@@ -343,8 +399,8 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
 					g_nancy->_sound->loadSound(_pushDownSound);
 				}
 				
-				if (_puzzleType == kOrdering) {
-					// Ordering puzzle allows for depressing buttons after they're pressed.
+				if (_puzzleType == kOrdering || _puzzleType == kKeypad) {
+					// 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) {
@@ -374,6 +430,8 @@ Common::String OrderingPuzzle::getRecordTypeName() const {
 		return "PianoPuzzle";
 	case kOrderItems:
 		return "OrderItemsPuzzle";
+	case kKeypad:
+		return "KeypadPuzzle";
 	default:
 		return "OrderingPuzzle";
 	}
diff --git a/engines/nancy/action/orderingpuzzle.h b/engines/nancy/action/orderingpuzzle.h
index 4d88ee3c797..ec125673b80 100644
--- a/engines/nancy/action/orderingpuzzle.h
+++ b/engines/nancy/action/orderingpuzzle.h
@@ -27,13 +27,18 @@
 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 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, and doubles the number of possible buttons.
 class OrderingPuzzle : public RenderActionRecord {
 public:
 	enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
-	enum PuzzleType { kOrdering, kPiano, kOrderItems };
+	enum PuzzleType { kOrdering, kPiano, kOrderItems, kKeypad };
 	OrderingPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
 	virtual ~OrderingPuzzle() {}
 
@@ -55,6 +60,9 @@ protected:
 	Common::String _imageName;
 	bool _hasSecondState = false;
 	bool _itemsStayDown = true;
+	bool _needButtonToCheckSuccess = false;
+	Common::Rect _checkButtonSrc;
+	Common::Rect _checkButtonDest;
 	Common::Array<Common::Rect> _down1Rects;
 	Common::Array<Common::Rect> _up2Rects;
 	Common::Array<Common::Rect> _down2Rects;
@@ -82,6 +90,7 @@ protected:
 	Common::Array<bool> _downItems;
 	Common::Array<bool> _secondStateItems;
 	Time _solveSoundPlayTime;
+	bool _checkButtonPressed = false;
 
 	PuzzleType _puzzleType;
 };


Commit: 09ef41280d0ec1bdd90e7911aed896b01c638a65
    https://github.com/scummvm/scummvm/commit/09ef41280d0ec1bdd90e7911aed896b01c638a65
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:29+03:00

Commit Message:
NANCY: Fix Overlay reading

This effectively reverts the last changes made to the class,
and replaces them with an actual fix, which is to only read
the _enableHotspot field in nancy2; later games seem to
have removed it entirely.

Changed paths:
    engines/nancy/action/overlay.cpp


diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index b663ee96c82..0fc78ed8308 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -48,10 +48,10 @@ void Overlay::readData(Common::SeekableReadStream &stream) {
 	uint16 numSrcRects = 0;
 
 	readFilename(ser, _imageName);
-	ser.skip(2, kGameTypeVampire, kGameTypeNancy2);
+	ser.skip(2); // VIDEO_STOP_RENDERING or VIDEO_CONTINUE_RENDERING
 	ser.syncAsUint16LE(_transparency);
 	ser.syncAsUint16LE(_hasSceneChange);
-	ser.syncAsUint16LE(_enableHotspot, kGameTypeNancy2);
+	ser.syncAsUint16LE(_enableHotspot, kGameTypeNancy2, kGameTypeNancy2);
 	ser.syncAsUint16LE(_z, kGameTypeNancy2);
 	ser.syncAsUint16LE(_overlayType, kGameTypeNancy2);
 	ser.syncAsUint16LE(numSrcRects, kGameTypeNancy2);
@@ -72,6 +72,12 @@ void Overlay::readData(Common::SeekableReadStream &stream) {
 
 	if (ser.getVersion() > kGameTypeNancy1) {
 		_isInterruptible = true;
+		
+		if (ser.getVersion() > kGameTypeNancy2) {
+			if (_overlayType == kPlayOverlayStatic) {
+				_enableHotspot = (_hasSceneChange == kPlayOverlaySceneChange) ? kPlayOverlayWithHotspot : kPlayOverlayNoHotspot;
+			}
+		}
 	}
 
 	if (_isInterruptible) {
@@ -134,7 +140,7 @@ void Overlay::execute() {
 							moveTo(_bitmaps[i].dest);
 							setVisible(true);
 
-							if ((_overlayType == kPlayOverlayStatic || g_nancy->getGameType() <= kGameTypeNancy1) && _enableHotspot == kPlayOverlayWithHotspot) {
+							if (_enableHotspot == kPlayOverlayWithHotspot) {
 								_hotspot = _screenPosition;
 								_hasHotspot = true;
 							}
@@ -206,7 +212,7 @@ void Overlay::execute() {
 		g_nancy->_sound->stopSound(_sound);
 
 		_flagsOnTrigger.execute();
-		if ((_overlayType == kPlayOverlayStatic || g_nancy->getGameType() <= kGameTypeNancy1) && _hasSceneChange == kPlayOverlaySceneChange) {
+		if (_hasSceneChange == kPlayOverlaySceneChange) {
 			NancySceneState.changeScene(_sceneChange);
 		}
 		


Commit: 2d1dd3b73129fc0184a06e0a8339c4ebe29b4463
    https://github.com/scummvm/scummvm/commit/2d1dd3b73129fc0184a06e0a8339c4ebe29b4463
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Implement TileMovePuzzle

Implemented the record type responsible for "unblock me"
type puzzles. Due to similarities in data and behavior, it
has been incorporated into the CollisionPuzzle class.

Changed paths:
    engines/nancy/action/arfactory.cpp
    engines/nancy/action/collisionpuzzle.cpp
    engines/nancy/action/collisionpuzzle.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 6039abd2f5e..4ba8e3ee735 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -212,9 +212,11 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 	case 210:
 		return new SafeLockPuzzle();
 	case 211:
-		return new CollisionPuzzle();
+		return new CollisionPuzzle(CollisionPuzzle::PuzzleType::kCollision);
 	case 212:
 		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kOrderItems);
+	case 213:
+		return new CollisionPuzzle(CollisionPuzzle::PuzzleType::kTileMove);
 	case 214:
 		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kKeypad);
 	default:
diff --git a/engines/nancy/action/collisionpuzzle.cpp b/engines/nancy/action/collisionpuzzle.cpp
index c33e144adcc..3a51dc519a8 100644
--- a/engines/nancy/action/collisionpuzzle.cpp
+++ b/engines/nancy/action/collisionpuzzle.cpp
@@ -44,20 +44,79 @@ void CollisionPuzzle::init() {
 	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
+	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);
+				}
+			}
 		}
-		_pieces[i].moveTo(pos);
-		_pieces[i]._gridPos = _startLocations[i];
-		_pieces[i].setVisible(true);
-		_pieces[i].setTransparent(true);
 	}
 
-	drawGrid();
+	if (_puzzleType == kCollision) {
+		drawGrid();
+	}
+
 	registerGraphics();
 }
 
@@ -80,7 +139,7 @@ void CollisionPuzzle::updateGraphics() {
 
 		int maxFrames = _framesPerMove * abs(diff);
 		if (_currentAnimFrame > maxFrames) {
-			if (_grid[_pieces[_currentlyAnimating]._gridPos.y][_pieces[_currentlyAnimating]._gridPos.x] == _currentlyAnimating + 1) {
+			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);
@@ -112,10 +171,19 @@ void CollisionPuzzle::updateGraphics() {
 
 void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
 	readFilename(stream, _imageName);
+	uint16 numPieces = 0;
 
 	uint16 width = stream.readUint16LE();
 	uint16 height = stream.readUint16LE();
-	uint16 numPieces = 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) {
@@ -126,19 +194,23 @@ void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
 	}
 	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();
+	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);
 	}
-	stream.skip((5 - numPieces) * 4);
-
-	readRectArray(stream, _pieceSrcs, numPieces, 5);
-	readRectArray(stream, _homeSrcs, numPieces, 5);
-	
-	readRect(stream, _verticalWallSrc);
-	readRect(stream, _horizontalWallSrc);
-	readRect(stream, _blockSrc);
 
 	_gridPos.x = stream.readUint32LE();
 	_gridPos.y = stream.readUint32LE();
@@ -149,7 +221,9 @@ void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
 	stream.skip(3);
 
 	_moveSound.readNormal(stream);
-	_homeSound.readNormal(stream);
+	if (_puzzleType == kCollision) {
+		_homeSound.readNormal(stream);
+	}
 	_wallHitSound.readNormal(stream);
 
 	_solveScene.readData(stream);
@@ -174,8 +248,21 @@ void CollisionPuzzle::execute() {
 			return;
 		}
 
-		for (uint i = 0; i < _pieces.size(); ++i) {
-			if (_grid[_pieces[i]._gridPos.y][_pieces[i]._gridPos.x] != i + 1) {
+		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;
 			}
 		}
@@ -258,8 +345,14 @@ Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
 		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;
-	for (int i = (horizontal ? newPos.x : newPos.y) + inc; i != lastPos; i += inc) {
+	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) {
@@ -268,12 +361,18 @@ Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
 			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;
 			}
 
-			if (_pieces[j]._gridPos == comparePos) {
+			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;
 			}
@@ -283,18 +382,20 @@ Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
 			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;
+		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;
 			}
-			
-			break;
-		} else if (evalVal == preStopWallType || evalVal == kBlock) {
-			break;
 		}
 
 		if (horizontal) {
@@ -304,13 +405,33 @@ Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
 		}
 	}
 
+	// 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 = _pieces[0]._drawSurface.getBounds();
+	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);
@@ -404,95 +525,115 @@ void CollisionPuzzle::handleInput(NancyInput &input) {
 		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);
+	if (_currentlyAnimating != -1) {
+		return;
+	}
 
 	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);
-				}
+		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));
+		}
 
-				return;
+		if (!left.isEmpty()) {
+			if (left.contains(input.mousePos)) {
+				checkPos = movePiece(i, kWallLeft);
+				if (checkPos != _pieces[i]._gridPos) {
+					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 (!right.isEmpty()) {
+			if (right.contains(input.mousePos)) {
+				checkPos = movePiece(i, kWallRight);
+				if (checkPos != _pieces[i]._gridPos) {
+					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);
-				}
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						_lastPosition = _pieces[i]._gridPos;
+						_pieces[i]._gridPos = checkPos;
+						_currentlyAnimating = i;
+						g_nancy->_sound->playSound(_moveSound);
+					}
 
-				return;
+					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 (!up.isEmpty()) {
+			if (up.contains(input.mousePos)) {
+				checkPos = movePiece(i, kWallUp);
+				if (checkPos != _pieces[i]._gridPos) {
+					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);
-				}
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						_lastPosition = _pieces[i]._gridPos;
+						_pieces[i]._gridPos = checkPos;
+						_currentlyAnimating = i;
+						g_nancy->_sound->playSound(_moveSound);
+					}
 
-				return;
+					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 (!down.isEmpty()) {
+			if (down.contains(input.mousePos)) {
+				checkPos = movePiece(i, kWallDown);
+				if (checkPos != _pieces[i]._gridPos) {
+					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);
-				}
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						_lastPosition = _pieces[i]._gridPos;
+						_pieces[i]._gridPos = checkPos;
+						_currentlyAnimating = i;
+						g_nancy->_sound->playSound(_moveSound);
+					}
 
-				return;
+					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);
 	}
 }
 
diff --git a/engines/nancy/action/collisionpuzzle.h b/engines/nancy/action/collisionpuzzle.h
index 5bbc3505de4..73977d1a959 100644
--- a/engines/nancy/action/collisionpuzzle.h
+++ b/engines/nancy/action/collisionpuzzle.h
@@ -27,9 +27,18 @@
 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:
-	CollisionPuzzle() : RenderActionRecord(7) {}
+	enum PuzzleType { kCollision, kTileMove };
+	CollisionPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
 	virtual ~CollisionPuzzle() {}
 
 	void init() override;
@@ -50,12 +59,14 @@ protected:
 		virtual ~Piece() {}
 
 		Common::Point _gridPos;
+		uint _w = 1;
+		uint _h = 1;
 	
 	protected:
 		bool isViewportRelative() const override { return true; }
 	};
 
-	Common::String getRecordTypeName() const override { return "CollisionPuzzle"; };
+	Common::String getRecordTypeName() const override { return _puzzleType == kCollision ? "CollisionPuzzle" : "TileMovePuzzle"; };
 	bool isViewportRelative() const override { return true; }
 
 	Common::Point movePiece(uint pieceID, WallType direction);
@@ -74,6 +85,9 @@ protected:
 	Common::Rect _horizontalWallSrc;
 	Common::Rect _blockSrc;
 
+	Common::Point _tileMoveExitPos = Common::Point(-1, -1);
+	uint _tileMoveExitSize = 0;
+
 	Common::Point _gridPos;
 
 	uint16 _lineWidth;
@@ -99,6 +113,8 @@ protected:
 
 	uint32 _solveSoundPlayTime = 0;
 	bool _solved = false;
+	
+	PuzzleType _puzzleType;
 };
 
 } // End of namespace Action


Commit: da0238d91bb5526790b0df456fdda2c9900d96bc
    https://github.com/scummvm/scummvm/commit/da0238d91bb5526790b0df456fdda2c9900d96bc
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Use correct exit cursor in nancy4 puzzles

Nancy4 started using the backwards arrow cursor for
puzzles' exit hotspots, which is now correctly implemented.

Changed paths:
    engines/nancy/action/collisionpuzzle.cpp
    engines/nancy/action/leverpuzzle.cpp
    engines/nancy/action/orderingpuzzle.cpp
    engines/nancy/action/overridelockpuzzle.cpp
    engines/nancy/action/passwordpuzzle.cpp
    engines/nancy/action/riddlepuzzle.cpp
    engines/nancy/action/rippedletterpuzzle.cpp
    engines/nancy/action/rotatinglockpuzzle.cpp
    engines/nancy/action/safelockpuzzle.cpp
    engines/nancy/action/sliderpuzzle.cpp
    engines/nancy/action/tangrampuzzle.cpp
    engines/nancy/action/telephone.cpp
    engines/nancy/action/towerpuzzle.cpp
    engines/nancy/action/turningpuzzle.cpp
    engines/nancy/cursor.cpp
    engines/nancy/cursor.h


diff --git a/engines/nancy/action/collisionpuzzle.cpp b/engines/nancy/action/collisionpuzzle.cpp
index 3a51dc519a8..0c41e7cb991 100644
--- a/engines/nancy/action/collisionpuzzle.cpp
+++ b/engines/nancy/action/collisionpuzzle.cpp
@@ -516,8 +516,7 @@ void CollisionPuzzle::handleInput(NancyInput &input) {
 	}
 
 	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);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/leverpuzzle.cpp b/engines/nancy/action/leverpuzzle.cpp
index fc7a0ffd04f..715ed4b3d39 100644
--- a/engines/nancy/action/leverpuzzle.cpp
+++ b/engines/nancy/action/leverpuzzle.cpp
@@ -154,7 +154,7 @@ void LeverPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/orderingpuzzle.cpp b/engines/nancy/action/orderingpuzzle.cpp
index aa1d57f79cb..8632c650775 100644
--- a/engines/nancy/action/orderingpuzzle.cpp
+++ b/engines/nancy/action/orderingpuzzle.cpp
@@ -349,7 +349,7 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/overridelockpuzzle.cpp b/engines/nancy/action/overridelockpuzzle.cpp
index f37ac8ad283..ec2ebc738b8 100644
--- a/engines/nancy/action/overridelockpuzzle.cpp
+++ b/engines/nancy/action/overridelockpuzzle.cpp
@@ -166,7 +166,7 @@ void OverrideLockPuzzle::handleInput(NancyInput &input) {
 
 	// Check the exit hotspot
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/passwordpuzzle.cpp b/engines/nancy/action/passwordpuzzle.cpp
index 99bcaef1f82..774c37e028a 100644
--- a/engines/nancy/action/passwordpuzzle.cpp
+++ b/engines/nancy/action/passwordpuzzle.cpp
@@ -189,7 +189,7 @@ void PasswordPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/riddlepuzzle.cpp b/engines/nancy/action/riddlepuzzle.cpp
index 08485f3d8f7..ec0915305ae 100644
--- a/engines/nancy/action/riddlepuzzle.cpp
+++ b/engines/nancy/action/riddlepuzzle.cpp
@@ -267,7 +267,7 @@ void RiddlePuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/rippedletterpuzzle.cpp b/engines/nancy/action/rippedletterpuzzle.cpp
index 5d502ec47f2..ccd651897e4 100644
--- a/engines/nancy/action/rippedletterpuzzle.cpp
+++ b/engines/nancy/action/rippedletterpuzzle.cpp
@@ -323,7 +323,7 @@ void RippedLetterPuzzle::handleInput(NancyInput &input) {
 	} else {
 		// No piece picked up, check the exit hotspot
 		if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-			g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+			g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 			if (input.input & NancyInput::kLeftMouseButtonUp) {
 				// Player has clicked, exit
diff --git a/engines/nancy/action/rotatinglockpuzzle.cpp b/engines/nancy/action/rotatinglockpuzzle.cpp
index f61ce6503e5..e2c810ca15b 100644
--- a/engines/nancy/action/rotatinglockpuzzle.cpp
+++ b/engines/nancy/action/rotatinglockpuzzle.cpp
@@ -165,7 +165,7 @@ void RotatingLockPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/safelockpuzzle.cpp b/engines/nancy/action/safelockpuzzle.cpp
index 43740a5a6ec..bef8649e6b6 100644
--- a/engines/nancy/action/safelockpuzzle.cpp
+++ b/engines/nancy/action/safelockpuzzle.cpp
@@ -175,7 +175,7 @@ void SafeLockPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/sliderpuzzle.cpp b/engines/nancy/action/sliderpuzzle.cpp
index 3a74fc6a4fc..b3d11b52ac1 100644
--- a/engines/nancy/action/sliderpuzzle.cpp
+++ b/engines/nancy/action/sliderpuzzle.cpp
@@ -169,7 +169,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/action/tangrampuzzle.cpp b/engines/nancy/action/tangrampuzzle.cpp
index b1395ba7791..e58efbf01e8 100644
--- a/engines/nancy/action/tangrampuzzle.cpp
+++ b/engines/nancy/action/tangrampuzzle.cpp
@@ -232,7 +232,7 @@ void TangramPuzzle::handleInput(NancyInput &input) {
 
 		// No tile under cursor, check exit hotspot
 		if (_exitHotspot.contains(mousePos)) {
-			g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+			g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 			if (input.input & NancyInput::kLeftMouseButtonUp) {
 				_state = kActionTrigger;
diff --git a/engines/nancy/action/telephone.cpp b/engines/nancy/action/telephone.cpp
index 5bda6b9e029..97026c93c89 100644
--- a/engines/nancy/action/telephone.cpp
+++ b/engines/nancy/action/telephone.cpp
@@ -239,7 +239,7 @@ void Telephone::handleInput(NancyInput &input) {
 	}
 
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			g_nancy->_sound->loadSound(_hangUpSound);
diff --git a/engines/nancy/action/towerpuzzle.cpp b/engines/nancy/action/towerpuzzle.cpp
index a4dff1bcc7e..095aba6b861 100644
--- a/engines/nancy/action/towerpuzzle.cpp
+++ b/engines/nancy/action/towerpuzzle.cpp
@@ -191,7 +191,7 @@ void TowerPuzzle::handleInput(NancyInput &input) {
 
 		// First, check the exit hotspot
 		if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-			g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+			g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 			if (input.input & NancyInput::kLeftMouseButtonUp) {
 				// Player has clicked, exit
diff --git a/engines/nancy/action/turningpuzzle.cpp b/engines/nancy/action/turningpuzzle.cpp
index e2889ad232e..272c674dfdb 100644
--- a/engines/nancy/action/turningpuzzle.cpp
+++ b/engines/nancy/action/turningpuzzle.cpp
@@ -249,7 +249,7 @@ void TurningPuzzle::execute() {
 
 void TurningPuzzle::handleInput(NancyInput &input) {
 	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
-		g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
 
 		if (input.input & NancyInput::kLeftMouseButtonUp) {
 			_state = kActionTrigger;
diff --git a/engines/nancy/cursor.cpp b/engines/nancy/cursor.cpp
index 884be28ab30..dabab50cd40 100644
--- a/engines/nancy/cursor.cpp
+++ b/engines/nancy/cursor.cpp
@@ -29,6 +29,15 @@
 
 namespace Nancy {
 
+CursorManager::CursorManager()  :
+	_isInitialized(false),
+	_curItemID(-1),
+	_curCursorType(kNormal),
+	_curCursorID(0),
+	_hasItem(false),
+	_numCursorTypes(0),
+	_puzzleExitCursor((g_nancy->getGameType() >= kGameTypeNancy4) ? kMoveBackward : kExit) {}
+
 void CursorManager::init(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index f73ed10c225..334b9bb667d 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -51,13 +51,7 @@ public:
 		kHotspotArrow
 	};
 
-	CursorManager() :
-		_isInitialized(false),
-		_curItemID(-1),
-		_curCursorType(kNormal),
-		_curCursorID(0),
-		_hasItem(false),
-		_numCursorTypes(0) {}
+	CursorManager();
 
 	void init(Common::SeekableReadStream *chunkStream);
 
@@ -73,6 +67,8 @@ public:
 	const Common::Rect &getPrimaryVideoInactiveZone() { return _primaryVideoInactiveZone; }
 	const Common::Point &getPrimaryVideoInitialPos() { return _primaryVideoInitialPos; }
 
+	const CursorType _puzzleExitCursor;
+
 private:
 	void showCursor(bool shouldShow);
 


Commit: 52b66621fa0aab068fccfdce2f18e0a56d6d1907
    https://github.com/scummvm/scummvm/commit/52b66621fa0aab068fccfdce2f18e0a56d6d1907
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Skip drawing first frame after loading save

Changed paths:
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 5bb6b3b0ce0..cf9d5407d9f 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -558,7 +558,7 @@ void Scene::synchronize(Common::Serializer &ser) {
 		}
 	}
 
-	// Sync sound data
+	g_nancy->_graphicsManager->suppressNextDraw();
 }
 
 UI::Clock *Scene::getClock() {


Commit: 7f8d6a47d3ec932bfbc00a65a88c928d95386e00
    https://github.com/scummvm/scummvm/commit/7f8d6a47d3ec932bfbc00a65a88c928d95386e00
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Avoid flashing cursor when changing scene

In cases where the mouse falls inside a hotspot with the
same cursor type in both the previous and next scenes,
we now avoid a single frame where the cursor flashes to
the default and then back.

Changed paths:
    engines/nancy/graphics.h
    engines/nancy/nancy.cpp
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index bd52fb8a784..ddd8915d9a7 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -33,6 +33,7 @@ class RenderObject;
 
 // Graphics class that handles multilayered surface rendering with minimal redraw
 class GraphicsManager {
+	friend class NancyEngine;
 public:
 	GraphicsManager();
 
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index a33ab81720d..b27f60cf13b 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -242,7 +242,11 @@ Common::Error NancyEngine::run() {
 	while (!shouldQuit()) {
 		uint32 frameEndTime = _system->getMillis() + 16;
 
-		_cursorManager->setCursorType(CursorManager::kNormalArrow);
+		bool graphicsWereSuppressed = _graphicsManager->_isSuppressed;
+		if (!graphicsWereSuppressed) {
+			_cursorManager->setCursorType(CursorManager::kNormalArrow);
+		}
+
 		_input->processEvents();
 
 		State::State *s;
@@ -276,39 +280,44 @@ Common::Error NancyEngine::run() {
 
 		_system->updateScreen();
 
-		// Use the spare time until the next frame to load larger data objects
-		// Some loading is guaranteed to happen even with no time left, to ensure
-		// slower systems won't be stuck waiting forever
-		if (_deferredLoaderObjects.size()) {
-			uint i = _deferredLoaderObjects.size() - 1;
-			int32 timePerObj = (frameEndTime - g_system->getMillis()) / _deferredLoaderObjects.size();
-
-			if (timePerObj < 0) {
-				timePerObj = 0;
-			}
+		// In cases where the graphics were not drawn for a frame, we want to make sure the next
+		// frame is processed as fast as possible. Thus, we skip deferred loaders and the time
+		// delay that normally maintains 60fps
+		if (!graphicsWereSuppressed) {
+			// Use the spare time until the next frame to load larger data objects
+			// Some loading is guaranteed to happen even with no time left, to ensure
+			// slower systems won't be stuck waiting forever
+			if (_deferredLoaderObjects.size()) {
+				uint i = _deferredLoaderObjects.size() - 1;
+				int32 timePerObj = (frameEndTime - g_system->getMillis()) / _deferredLoaderObjects.size();
+
+				if (timePerObj < 0) {
+					timePerObj = 0;
+				}
 
-			for (auto *iter = _deferredLoaderObjects.begin(); iter < _deferredLoaderObjects.end(); ++iter) {
-				if (iter->expired()) {
-					iter = _deferredLoaderObjects.erase(iter);
-				} else {
-					auto objectPtr = iter->lock();
-					if (objectPtr) {
-						if (objectPtr->load(frameEndTime - (i * timePerObj))) {
-							iter = _deferredLoaderObjects.erase(iter);
+				for (auto *iter = _deferredLoaderObjects.begin(); iter < _deferredLoaderObjects.end(); ++iter) {
+					if (iter->expired()) {
+						iter = _deferredLoaderObjects.erase(iter);
+					} else {
+						auto objectPtr = iter->lock();
+						if (objectPtr) {
+							if (objectPtr->load(frameEndTime - (i * timePerObj))) {
+								iter = _deferredLoaderObjects.erase(iter);
+							}
+							--i;
 						}
-						--i;
-					}
 
-					if (_system->getMillis() > frameEndTime) {
-						break;
+						if (_system->getMillis() > frameEndTime) {
+							break;
+						}
 					}
 				}
 			}
-		}
 
-		uint32 frameFinishTime = _system->getMillis();
-		if (frameFinishTime < frameEndTime) {
-			_system->delayMillis(frameEndTime - frameFinishTime);
+			uint32 frameFinishTime = _system->getMillis();
+			if (frameFinishTime < frameEndTime) {
+				_system->delayMillis(frameEndTime - frameFinishTime);
+			}
 		}
 	}
 
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index cf9d5407d9f..8ca5120c5e8 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -736,6 +736,7 @@ void Scene::load() {
 	_flags.sceneCounts.getOrCreateVal(_sceneState.currentScene.sceneID)++;
 
 	g_nancy->_sound->recalculateSoundEffects();
+	g_nancy->_graphicsManager->suppressNextDraw();
 
 	_state = kStartSound;
 }


Commit: aecaf3591f15af0ce05393cbe4faae3b00adffd1
    https://github.com/scummvm/scummvm/commit/aecaf3591f15af0ce05393cbe4faae3b00adffd1
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Reorganize ActionRecords

Moved all puzzle action records to a separate folder. Split
recordtypes.h into three different files, depending on record
function: navigation, sound, and miscellaneous.

Changed paths:
  A engines/nancy/action/miscrecords.cpp
  A engines/nancy/action/miscrecords.h
  A engines/nancy/action/navigationrecords.cpp
  A engines/nancy/action/navigationrecords.h
  A engines/nancy/action/puzzle/bombpuzzle.cpp
  A engines/nancy/action/puzzle/bombpuzzle.h
  A engines/nancy/action/puzzle/collisionpuzzle.cpp
  A engines/nancy/action/puzzle/collisionpuzzle.h
  A engines/nancy/action/puzzle/leverpuzzle.cpp
  A engines/nancy/action/puzzle/leverpuzzle.h
  A engines/nancy/action/puzzle/orderingpuzzle.cpp
  A engines/nancy/action/puzzle/orderingpuzzle.h
  A engines/nancy/action/puzzle/overridelockpuzzle.cpp
  A engines/nancy/action/puzzle/overridelockpuzzle.h
  A engines/nancy/action/puzzle/passwordpuzzle.cpp
  A engines/nancy/action/puzzle/passwordpuzzle.h
  A engines/nancy/action/puzzle/raycastpuzzle.cpp
  A engines/nancy/action/puzzle/raycastpuzzle.h
  A engines/nancy/action/puzzle/riddlepuzzle.cpp
  A engines/nancy/action/puzzle/riddlepuzzle.h
  A engines/nancy/action/puzzle/rippedletterpuzzle.cpp
  A engines/nancy/action/puzzle/rippedletterpuzzle.h
  A engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
  A engines/nancy/action/puzzle/rotatinglockpuzzle.h
  A engines/nancy/action/puzzle/safelockpuzzle.cpp
  A engines/nancy/action/puzzle/safelockpuzzle.h
  A engines/nancy/action/puzzle/setplayerclock.cpp
  A engines/nancy/action/puzzle/setplayerclock.h
  A engines/nancy/action/puzzle/sliderpuzzle.cpp
  A engines/nancy/action/puzzle/sliderpuzzle.h
  A engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
  A engines/nancy/action/puzzle/soundequalizerpuzzle.h
  A engines/nancy/action/puzzle/tangrampuzzle.cpp
  A engines/nancy/action/puzzle/tangrampuzzle.h
  A engines/nancy/action/puzzle/telephone.cpp
  A engines/nancy/action/puzzle/telephone.h
  A engines/nancy/action/puzzle/towerpuzzle.cpp
  A engines/nancy/action/puzzle/towerpuzzle.h
  A engines/nancy/action/puzzle/turningpuzzle.cpp
  A engines/nancy/action/puzzle/turningpuzzle.h
  A engines/nancy/action/soundrecords.cpp
  A engines/nancy/action/soundrecords.h
  R engines/nancy/action/bombpuzzle.cpp
  R engines/nancy/action/bombpuzzle.h
  R engines/nancy/action/collisionpuzzle.cpp
  R engines/nancy/action/collisionpuzzle.h
  R engines/nancy/action/leverpuzzle.cpp
  R engines/nancy/action/leverpuzzle.h
  R engines/nancy/action/orderingpuzzle.cpp
  R engines/nancy/action/orderingpuzzle.h
  R engines/nancy/action/overridelockpuzzle.cpp
  R engines/nancy/action/overridelockpuzzle.h
  R engines/nancy/action/passwordpuzzle.cpp
  R engines/nancy/action/passwordpuzzle.h
  R engines/nancy/action/raycastpuzzle.cpp
  R engines/nancy/action/raycastpuzzle.h
  R engines/nancy/action/recordtypes.cpp
  R engines/nancy/action/recordtypes.h
  R engines/nancy/action/riddlepuzzle.cpp
  R engines/nancy/action/riddlepuzzle.h
  R engines/nancy/action/rippedletterpuzzle.cpp
  R engines/nancy/action/rippedletterpuzzle.h
  R engines/nancy/action/rotatinglockpuzzle.cpp
  R engines/nancy/action/rotatinglockpuzzle.h
  R engines/nancy/action/safelockpuzzle.cpp
  R engines/nancy/action/safelockpuzzle.h
  R engines/nancy/action/setplayerclock.cpp
  R engines/nancy/action/setplayerclock.h
  R engines/nancy/action/sliderpuzzle.cpp
  R engines/nancy/action/sliderpuzzle.h
  R engines/nancy/action/soundequalizerpuzzle.cpp
  R engines/nancy/action/soundequalizerpuzzle.h
  R engines/nancy/action/tangrampuzzle.cpp
  R engines/nancy/action/tangrampuzzle.h
  R engines/nancy/action/telephone.cpp
  R engines/nancy/action/telephone.h
  R engines/nancy/action/towerpuzzle.cpp
  R engines/nancy/action/towerpuzzle.h
  R engines/nancy/action/turningpuzzle.cpp
  R engines/nancy/action/turningpuzzle.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 4ba8e3ee735..4178a12cba5 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -19,29 +19,33 @@
  *
  */
 
-#include "engines/nancy/action/recordtypes.h"
+#include "engines/nancy/action/navigationrecords.h"
+#include "engines/nancy/action/soundrecords.h"
+#include "engines/nancy/action/miscrecords.h"
+
 #include "engines/nancy/action/conversation.h"
+#include "engines/nancy/action/overlay.h"
 #include "engines/nancy/action/secondaryvideo.h"
 #include "engines/nancy/action/secondarymovie.h"
-#include "engines/nancy/action/overlay.h"
-#include "engines/nancy/action/orderingpuzzle.h"
-#include "engines/nancy/action/rotatinglockpuzzle.h"
-#include "engines/nancy/action/telephone.h"
-#include "engines/nancy/action/sliderpuzzle.h"
-#include "engines/nancy/action/passwordpuzzle.h"
-#include "engines/nancy/action/leverpuzzle.h"
-#include "engines/nancy/action/rippedletterpuzzle.h"
-#include "engines/nancy/action/towerpuzzle.h"
-#include "engines/nancy/action/riddlepuzzle.h"
-#include "engines/nancy/action/overridelockpuzzle.h"
-#include "engines/nancy/action/bombpuzzle.h"
-#include "engines/nancy/action/soundequalizerpuzzle.h"
-#include "engines/nancy/action/setplayerclock.h"
-#include "engines/nancy/action/raycastpuzzle.h"
-#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/action/puzzle/bombpuzzle.h"
+#include "engines/nancy/action/puzzle/collisionpuzzle.h"
+#include "engines/nancy/action/puzzle/leverpuzzle.h"
+#include "engines/nancy/action/puzzle/orderingpuzzle.h"
+#include "engines/nancy/action/puzzle/overridelockpuzzle.h"
+#include "engines/nancy/action/puzzle/passwordpuzzle.h"
+#include "engines/nancy/action/puzzle/raycastpuzzle.h"
+#include "engines/nancy/action/puzzle/riddlepuzzle.h"
+#include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
+#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
+#include "engines/nancy/action/puzzle/safelockpuzzle.h"
+#include "engines/nancy/action/puzzle/setplayerclock.h"
+#include "engines/nancy/action/puzzle/sliderpuzzle.h"
+#include "engines/nancy/action/puzzle/soundequalizerpuzzle.h"
+#include "engines/nancy/action/puzzle/tangrampuzzle.h"
+#include "engines/nancy/action/puzzle/telephone.h"
+#include "engines/nancy/action/puzzle/towerpuzzle.h"
+#include "engines/nancy/action/puzzle/turningpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/recordtypes.cpp b/engines/nancy/action/miscrecords.cpp
similarity index 57%
rename from engines/nancy/action/recordtypes.cpp
rename to engines/nancy/action/miscrecords.cpp
index 75ba07e1e08..e0870ce2bdb 100644
--- a/engines/nancy/action/recordtypes.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -24,7 +24,7 @@
 #include "engines/nancy/resource.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/recordtypes.h"
+#include "engines/nancy/action/miscrecords.h"
 
 #include "engines/nancy/state/scene.h"
 
@@ -39,185 +39,6 @@ void Unimplemented::execute() {
 	_isDone = true;
 }
 
-void SceneChange::readData(Common::SeekableReadStream &stream) {
-	_sceneChange.readData(stream);
-}
-
-void SceneChange::execute() {
-	NancySceneState.changeScene(_sceneChange);
-	_isDone = true;
-}
-
-void HotMultiframeSceneChange::readData(Common::SeekableReadStream &stream) {
-	SceneChange::readData(stream);
-	uint16 numHotspots = stream.readUint16LE();
-
-	_hotspots.reserve(numHotspots);
-	for (uint i = 0; i < numHotspots; ++i) {
-		_hotspots.push_back(HotspotDescription());
-		HotspotDescription &newDesc = _hotspots[i];
-		newDesc.readData(stream);
-	}
-}
-
-void HotMultiframeSceneChange::execute() {
-	switch (_state) {
-	case kBegin:
-		// turn main rendering on
-		_state = kRun;
-		// fall through
-	case kRun:
-		_hasHotspot = false;
-		for (uint i = 0; i < _hotspots.size(); ++i) {
-			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
-				_hasHotspot = true;
-				_hotspot = _hotspots[i].coords;
-			}
-		}
-		break;
-	case kActionTrigger:
-		SceneChange::execute();
-		break;
-	}
-}
-
-void Hot1FrSceneChange::readData(Common::SeekableReadStream &stream) {
-	SceneChange::readData(stream);
-	_hotspotDesc.readData(stream);
-}
-
-void Hot1FrSceneChange::execute() {
-	switch (_state) {
-	case kBegin:
-		_hotspot = _hotspotDesc.coords;
-		_state = kRun;
-		// fall through
-	case kRun:
-		if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
-			_hasHotspot = true;
-		} else {
-			_hasHotspot = false;
-		}
-		break;
-	case kActionTrigger:
-		SceneChange::execute();
-		break;
-	}
-}
-
-void HotMultiframeMultisceneChange::readData(Common::SeekableReadStream &stream) {
-	_onTrue.readData(stream);
-	_onFalse.readData(stream);
-	_condType = stream.readByte();
-	_conditionID = stream.readUint16LE();
-	_conditionPayload = stream.readByte();
-	uint numHotspots = stream.readUint16LE();
-
-	_hotspots.resize(numHotspots);
-
-	for (uint i = 0; i < numHotspots; ++i) {
-		_hotspots[i].readData(stream);
-	}
-}
-
-void HotMultiframeMultisceneChange::execute() {
-	switch (_state) {
-	case kBegin:
-		// set something to 1
-		_state = kRun;
-		// fall through
-	case kRun:
-		_hasHotspot = false;
-
-		for (HotspotDescription &desc : _hotspots) {
-			if (desc.frameID == NancySceneState.getSceneInfo().frameID) {
-				_hotspot = desc.coords;
-				_hasHotspot = true;
-			}
-		}
-
-		break;
-	case kActionTrigger: {
-		bool conditionMet = false;
-		switch (_condType) {
-		case kFlagEvent:
-			if (NancySceneState.getEventFlag(_conditionID, _conditionPayload)) {
-				conditionMet = true;
-			}
-			break;
-		case kFlagInventory:
-			if (NancySceneState.hasItem(_conditionID) == _conditionPayload) {
-				conditionMet = true;
-			}
-			break;
-		case kFlagCursor:
-			if (NancySceneState.getHeldItem() == _conditionPayload) {
-				conditionMet = true;
-			}
-			break;
-		}
-
-		if (conditionMet) {
-			NancySceneState.changeScene(_onTrue);
-		} else {
-			NancySceneState.changeScene(_onFalse);
-		}
-
-		break;
-	}
-	}
-}
-
-void HotMultiframeMultisceneCursorTypeSceneChange::readData(Common::SeekableReadStream &stream) {
-	uint16 numScenes = stream.readUint16LE();
-	_scenes.resize(numScenes);
-	_cursorTypes.resize(numScenes);
-	for (uint i = 0; i < numScenes; ++i) {
-		_cursorTypes[i] = stream.readUint16LE();
-		_scenes[i].readData(stream);
-	}
-
-	stream.skip(2);
-	_defaultScene.readData(stream);
-	
-	uint16 numHotspots = stream.readUint16LE();
-	_hotspots.resize(numHotspots);
-	for (uint i = 0; i < numHotspots; ++i) {
-		_hotspots[i].readData(stream);
-	}
-}
-
-void HotMultiframeMultisceneCursorTypeSceneChange::execute() {
-	switch (_state) {
-	case kBegin:
-		// turn main rendering on
-		_state = kRun;
-		// fall through
-	case kRun:
-		_hasHotspot = false;
-		for (uint i = 0; i < _hotspots.size(); ++i) {
-			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
-				_hasHotspot = true;
-				_hotspot = _hotspots[i].coords;
-			}
-		}
-		break;
-	case kActionTrigger:
-		for (uint i = 0; i < _cursorTypes.size(); ++i) {
-			if (NancySceneState.getHeldItem() == _cursorTypes[i]) {
-				NancySceneState.changeScene(_scenes[i]);
-
-				_isDone = true;
-				return;
-			}
-		}
-
-		NancySceneState.changeScene(_defaultScene);
-		_isDone = true;
-		break;
-	}
-}
-
 void PaletteThisScene::readData(Common::SeekableReadStream &stream) {
 	_paletteID = stream.readByte();
 	_unknownEnum = stream.readByte();
@@ -273,66 +94,6 @@ void LightningOn::execute() {
 	_isDone = true;
 }
 
-void MapCall::readData(Common::SeekableReadStream &stream) {
-	stream.skip(1);
-}
-
-void MapCall::execute() {
-	_execType = kRepeating;
-	NancySceneState.requestStateChange(NancyState::kMap);
-	finishExecution();
-}
-
-void MapCallHot1Fr::readData(Common::SeekableReadStream &stream) {
-	_hotspotDesc.readData(stream);
-}
-
-void MapCallHot1Fr::execute() {
-	switch (_state) {
-	case kBegin:
-		_hotspot = _hotspotDesc.coords;
-		_state = kRun;
-		// fall through
-	case kRun:
-		if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
-			_hasHotspot = true;
-		}
-		break;
-	case kActionTrigger:
-		MapCall::execute();
-		break;
-	}
-}
-
-void MapCallHotMultiframe::readData(Common::SeekableReadStream &stream) {
-	uint16 numDescs = stream.readUint16LE();
-	_hotspots.reserve(numDescs);
-	for (uint i = 0; i < numDescs; ++i) {
-		_hotspots.push_back(HotspotDescription());
-		_hotspots[i].readData(stream);
-	}
-}
-
-void MapCallHotMultiframe::execute() {
-	switch (_state) {
-	case kBegin:
-		_state = kRun;
-		// fall through
-	case kRun:
-		_hasHotspot = false;
-		for (uint i = 0; i < _hotspots.size(); ++i) {
-			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
-				_hasHotspot = true;
-				_hotspot = _hotspots[i].coords;
-			}
-		}
-		break;
-	case kActionTrigger:
-		MapCall::execute();
-		break;
-	}
-}
-
 void TextBoxWrite::readData(Common::SeekableReadStream &stream) {
 	uint16 size = stream.readUint16LE();
 
@@ -624,141 +385,6 @@ void ShowInventoryItem::execute() {
 	}
 }
 
-void PlayDigiSoundAndDie::readData(Common::SeekableReadStream &stream) {
-	_sound.readDIGI(stream);
-
-	if (g_nancy->getGameType() >= kGameTypeNancy3) {
-		_soundEffect = new SoundEffectDescription;
-		_soundEffect->readData(stream);
-	}
-
-	_sceneChange.readData(stream, g_nancy->getGameType() == kGameTypeVampire);
-
-	_flagOnTrigger.label = stream.readSint16LE();
-	_flagOnTrigger.flag = stream.readByte();
-	stream.skip(2);
-}
-
-void PlayDigiSoundAndDie::execute() {
-	switch (_state) {
-	case kBegin:
-		g_nancy->_sound->loadSound(_sound, &_soundEffect);
-		g_nancy->_sound->playSound(_sound);
-		_state = kRun;
-		break;
-	case kRun:
-		if (!g_nancy->_sound->isSoundPlaying(_sound)) {
-			_state = kActionTrigger;
-		}
-
-		break;
-	case kActionTrigger:
-		if (_sceneChange.sceneID != 9999) {
-			NancySceneState.changeScene(_sceneChange);
-		}
-
-		NancySceneState.setEventFlag(_flagOnTrigger);
-		g_nancy->_sound->stopSound(_sound);
-
-		finishExecution();
-		break;
-	}
-}
-
-void PlayDigiSoundCC::readData(Common::SeekableReadStream &stream) {
-	PlayDigiSoundAndDie::readData(stream);
-
-	uint16 textSize = stream.readUint16LE();
-	if (textSize) {
-		char *strBuf = new char[textSize];
-		stream.read(strBuf, textSize);
-		UI::Textbox::assembleTextLine(strBuf, _ccText, textSize);
-		delete[] strBuf;
-	}
-}
-
-void PlayDigiSoundCC::execute() {
-	if (_state == kBegin) {
-		NancySceneState.getTextbox().clear();
-		NancySceneState.getTextbox().addTextLine(_ccText);
-	}
-	PlayDigiSoundAndDie::execute();
-}
-
-void PlaySoundPanFrameAnchorAndDie::readData(Common::SeekableReadStream &stream) {
-	_sound.readDIGI(stream);
-	stream.skip(2);
-	_sound.isPanning = true;
-}
-
-void PlaySoundPanFrameAnchorAndDie::execute() {
-	g_nancy->_sound->loadSound(_sound);
-	g_nancy->_sound->playSound(_sound);
-	_isDone = true;
-}
-
-void PlaySoundMultiHS::readData(Common::SeekableReadStream &stream) {
-	_sound.readNormal(stream);
-
-	if (g_nancy->getGameType() != kGameTypeVampire) {
-		_sceneChange.readData(stream);
-		_flag.label = stream.readSint16LE();
-		_flag.flag = stream.readByte();
-		stream.skip(2);
-	} else {
-		_flag.label = kEvNoEvent;
-		_sceneChange.sceneID = 9999;
-	}
-
-	uint16 numHotspots = stream.readUint16LE();
-
-	_hotspots.reserve(numHotspots);
-	for (uint i = 0; i < numHotspots; ++i) {
-		_hotspots.push_back(HotspotDescription());
-		_hotspots.back().frameID = stream.readUint16LE();
-		readRect(stream, _hotspots.back().coords);
-	}
-}
-
-void PlaySoundMultiHS::execute() {
-	switch (_state) {
-	case kBegin:
-		_state = kRun;
-		// fall through
-	case kRun: {
-		_hasHotspot = false;
-		uint currentFrame = NancySceneState.getSceneInfo().frameID;
-
-		for (uint i = 0; i < _hotspots.size(); ++i) {
-			if (_hotspots[i].frameID == currentFrame) {
-				_hotspot = _hotspots[i].coords;
-				_hasHotspot = true;
-				break;
-			}
-		}
-
-		break;
-	}
-	case kActionTrigger:
-		g_nancy->_sound->loadSound(_sound);
-		g_nancy->_sound->playSound(_sound);
-		NancySceneState.changeScene(_sceneChange);
-		NancySceneState.setEventFlag(_flag);
-		finishExecution();
-		break;
-	}
-}
-
-void StopSound::readData(Common::SeekableReadStream &stream) {
-	_channelID = stream.readUint16LE();
-	_sceneChange.readData(stream);
-}
-
-void StopSound::execute() {
-	g_nancy->_sound->stopSound(_channelID);
-	_sceneChange.execute();
-}
-
 void HintSystem::readData(Common::SeekableReadStream &stream) {
 	_characterID = stream.readByte();
 	_genericSound.readNormal(stream);
diff --git a/engines/nancy/action/recordtypes.h b/engines/nancy/action/miscrecords.h
similarity index 57%
rename from engines/nancy/action/recordtypes.h
rename to engines/nancy/action/miscrecords.h
index 035bd82ce61..e1e8d5daf4d 100644
--- a/engines/nancy/action/recordtypes.h
+++ b/engines/nancy/action/miscrecords.h
@@ -34,115 +34,6 @@ class Unimplemented : public ActionRecord {
 	void execute() override;
 };
 
-class SceneChange : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	SceneChangeDescription _sceneChange;
-
-protected:
-	Common::String getRecordTypeName() const override { return "SceneChange"; }
-};
-
-class HotMultiframeSceneChange : public SceneChange {
-public:
-	HotMultiframeSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
-	virtual ~HotMultiframeSceneChange() {}
-
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	Common::Array<HotspotDescription> _hotspots;
-
-protected:
-	bool canHaveHotspot() const override { return true; }
-	Common::String getRecordTypeName() const override {
-		switch (_hoverCursor) {
-		case CursorManager::kMoveForward:
-			return "HotMultiframeForwardSceneChange";
-		case CursorManager::kMoveUp:
-			return "HotMultiframeUpSceneChange";
-		case CursorManager::kMoveDown:
-			return "HotMultiframeDownSceneChange";
-		default:
-			return "HotMultiframeSceneChange";
-		}
-	}
-
-	CursorManager::CursorType _hoverCursor;
-};
-
-class Hot1FrSceneChange : public SceneChange {
-public:
-	Hot1FrSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
-	virtual ~Hot1FrSceneChange() {}
-
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-	
-	CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
-
-	HotspotDescription _hotspotDesc;
-
-protected:
-	bool canHaveHotspot() const override { return true; }
-	Common::String getRecordTypeName() const override {
-		switch (_hoverCursor) {
-		case CursorManager::kExit:
-			return "Hot1FrExitSceneChange";
-		case CursorManager::kMoveForward:
-			return "Hot1FrForwardSceneChange";
-		case CursorManager::kMoveBackward:
-			return "Hot1FrBackSceneChange";
-		case CursorManager::kMoveUp:
-			return "Hot1FrUpSceneChange";
-		case CursorManager::kMoveDown:
-			return "Hot1FrDownSceneChange";
-		case CursorManager::kTurnLeft:
-			return "Hot1FrLeftSceneChange";
-		case CursorManager::kTurnRight:
-			return "Hot1FrUpSceneChange";
-		default:
-			return "Hot1FrSceneChange";
-		}
-	}
-
-	CursorManager::CursorType _hoverCursor;
-};
-
-class HotMultiframeMultisceneChange : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	SceneChangeDescription _onTrue;
-	SceneChangeDescription _onFalse;
-	byte _condType;
-	uint16 _conditionID;
-	byte _conditionPayload;
-	Common::Array<HotspotDescription> _hotspots;
-
-protected:
-	bool canHaveHotspot() const override { return true; }
-	Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneChange"; }
-};
-
-class HotMultiframeMultisceneCursorTypeSceneChange : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	Common::Array<SceneChangeDescription> _scenes;
-	Common::Array<uint16> _cursorTypes;
-
-	SceneChangeDescription _defaultScene;
-	Common::Array<HotspotDescription> _hotspots;
-
-protected:
-	Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneCursorTypeSceneChange"; }
-};
-
 class PaletteThisScene : public ActionRecord {
 public:
 	void readData(Common::SeekableReadStream &stream) override;
@@ -194,41 +85,6 @@ protected:
 	Common::String getRecordTypeName() const override { return "SpecialEffect"; }
 };
 
-class MapCall : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	CursorManager::CursorType getHoverCursor() const override { return CursorManager::kExit; }
-
-protected:
-	Common::String getRecordTypeName() const override { return "MapCall"; }
-};
-
-class MapCallHot1Fr : public MapCall {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	HotspotDescription _hotspotDesc;
-
-protected:
-	bool canHaveHotspot() const override { return true; }
-	Common::String getRecordTypeName() const override { return "MapCallHot1Fr"; }
-};
-
-class MapCallHotMultiframe : public MapCall {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	Common::Array<HotspotDescription> _hotspots;
-
-protected:
-	bool canHaveHotspot() const override { return true; }
-	Common::String getRecordTypeName() const override { return "MapCallHotMultiframe"; }
-};
-
 class TextBoxWrite : public ActionRecord {
 public:
 	void readData(Common::SeekableReadStream &stream) override;
@@ -429,71 +285,6 @@ protected:
 	bool isViewportRelative() const override { return true; }
 };
 
-class PlayDigiSoundAndDie : public ActionRecord {
-public:
-	PlayDigiSoundAndDie() {}
-	~PlayDigiSoundAndDie() { delete _soundEffect; }
-	
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	SoundDescription _sound;
-	SoundEffectDescription *_soundEffect = nullptr;
-	SceneChangeDescription _sceneChange;
-	FlagDescription _flagOnTrigger;
-
-protected:
-	Common::String getRecordTypeName() const override { return "PlayDigiSoundAndDie"; }
-};
-
-class PlayDigiSoundCC : public PlayDigiSoundAndDie {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	Common::String _ccText;
-
-protected:
-	Common::String getRecordTypeName() const override { return "PlayDigiSoundCC"; }
-};
-
-class PlaySoundPanFrameAnchorAndDie : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	SoundDescription _sound;
-
-protected:
-	Common::String getRecordTypeName() const override { return "PlaySoundPanFrameAnchorAndDie"; }
-};
-
-class PlaySoundMultiHS : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	SoundDescription _sound; // 0x0
-	SceneChangeDescription _sceneChange; // 0x22
-	FlagDescription _flag; // 0x2A
-	Common::Array<HotspotDescription> _hotspots; // 0x31
-
-protected:
-	Common::String getRecordTypeName() const override { return "PlaySoundMultiHS"; }
-};
-
-class StopSound : public ActionRecord {
-public:
-	void readData(Common::SeekableReadStream &stream) override;
-	void execute() override;
-
-	uint _channelID;
-	SceneChangeWithFlag _sceneChange;
-
-protected:
-	Common::String getRecordTypeName() const override { return "StopSound"; }
-};
-
 class HintSystem : public ActionRecord {
 public:
 	void readData(Common::SeekableReadStream &stream) override;
diff --git a/engines/nancy/action/navigationrecords.cpp b/engines/nancy/action/navigationrecords.cpp
new file mode 100644
index 00000000000..d62d8107668
--- /dev/null
+++ b/engines/nancy/action/navigationrecords.cpp
@@ -0,0 +1,272 @@
+/* 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/util.h"
+
+#include "engines/nancy/action/navigationrecords.h"
+
+#include "engines/nancy/state/scene.h"
+
+namespace Nancy {
+namespace Action {
+
+void SceneChange::readData(Common::SeekableReadStream &stream) {
+	_sceneChange.readData(stream);
+}
+
+void SceneChange::execute() {
+	NancySceneState.changeScene(_sceneChange);
+	_isDone = true;
+}
+
+void HotMultiframeSceneChange::readData(Common::SeekableReadStream &stream) {
+	SceneChange::readData(stream);
+	uint16 numHotspots = stream.readUint16LE();
+
+	_hotspots.reserve(numHotspots);
+	for (uint i = 0; i < numHotspots; ++i) {
+		_hotspots.push_back(HotspotDescription());
+		HotspotDescription &newDesc = _hotspots[i];
+		newDesc.readData(stream);
+	}
+}
+
+void HotMultiframeSceneChange::execute() {
+	switch (_state) {
+	case kBegin:
+		// turn main rendering on
+		_state = kRun;
+		// fall through
+	case kRun:
+		_hasHotspot = false;
+		for (uint i = 0; i < _hotspots.size(); ++i) {
+			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
+				_hasHotspot = true;
+				_hotspot = _hotspots[i].coords;
+			}
+		}
+		break;
+	case kActionTrigger:
+		SceneChange::execute();
+		break;
+	}
+}
+
+void Hot1FrSceneChange::readData(Common::SeekableReadStream &stream) {
+	SceneChange::readData(stream);
+	_hotspotDesc.readData(stream);
+}
+
+void Hot1FrSceneChange::execute() {
+	switch (_state) {
+	case kBegin:
+		_hotspot = _hotspotDesc.coords;
+		_state = kRun;
+		// fall through
+	case kRun:
+		if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
+			_hasHotspot = true;
+		} else {
+			_hasHotspot = false;
+		}
+		break;
+	case kActionTrigger:
+		SceneChange::execute();
+		break;
+	}
+}
+
+void HotMultiframeMultisceneChange::readData(Common::SeekableReadStream &stream) {
+	_onTrue.readData(stream);
+	_onFalse.readData(stream);
+	_condType = stream.readByte();
+	_conditionID = stream.readUint16LE();
+	_conditionPayload = stream.readByte();
+	uint numHotspots = stream.readUint16LE();
+
+	_hotspots.resize(numHotspots);
+
+	for (uint i = 0; i < numHotspots; ++i) {
+		_hotspots[i].readData(stream);
+	}
+}
+
+void HotMultiframeMultisceneChange::execute() {
+	switch (_state) {
+	case kBegin:
+		// set something to 1
+		_state = kRun;
+		// fall through
+	case kRun:
+		_hasHotspot = false;
+
+		for (HotspotDescription &desc : _hotspots) {
+			if (desc.frameID == NancySceneState.getSceneInfo().frameID) {
+				_hotspot = desc.coords;
+				_hasHotspot = true;
+			}
+		}
+
+		break;
+	case kActionTrigger: {
+		bool conditionMet = false;
+		switch (_condType) {
+		case kFlagEvent:
+			if (NancySceneState.getEventFlag(_conditionID, _conditionPayload)) {
+				conditionMet = true;
+			}
+			break;
+		case kFlagInventory:
+			if (NancySceneState.hasItem(_conditionID) == _conditionPayload) {
+				conditionMet = true;
+			}
+			break;
+		case kFlagCursor:
+			if (NancySceneState.getHeldItem() == _conditionPayload) {
+				conditionMet = true;
+			}
+			break;
+		}
+
+		if (conditionMet) {
+			NancySceneState.changeScene(_onTrue);
+		} else {
+			NancySceneState.changeScene(_onFalse);
+		}
+
+		break;
+	}
+	}
+}
+
+void HotMultiframeMultisceneCursorTypeSceneChange::readData(Common::SeekableReadStream &stream) {
+	uint16 numScenes = stream.readUint16LE();
+	_scenes.resize(numScenes);
+	_cursorTypes.resize(numScenes);
+	for (uint i = 0; i < numScenes; ++i) {
+		_cursorTypes[i] = stream.readUint16LE();
+		_scenes[i].readData(stream);
+	}
+
+	stream.skip(2);
+	_defaultScene.readData(stream);
+	
+	uint16 numHotspots = stream.readUint16LE();
+	_hotspots.resize(numHotspots);
+	for (uint i = 0; i < numHotspots; ++i) {
+		_hotspots[i].readData(stream);
+	}
+}
+
+void HotMultiframeMultisceneCursorTypeSceneChange::execute() {
+	switch (_state) {
+	case kBegin:
+		// turn main rendering on
+		_state = kRun;
+		// fall through
+	case kRun:
+		_hasHotspot = false;
+		for (uint i = 0; i < _hotspots.size(); ++i) {
+			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
+				_hasHotspot = true;
+				_hotspot = _hotspots[i].coords;
+			}
+		}
+		break;
+	case kActionTrigger:
+		for (uint i = 0; i < _cursorTypes.size(); ++i) {
+			if (NancySceneState.getHeldItem() == _cursorTypes[i]) {
+				NancySceneState.changeScene(_scenes[i]);
+
+				_isDone = true;
+				return;
+			}
+		}
+
+		NancySceneState.changeScene(_defaultScene);
+		_isDone = true;
+		break;
+	}
+}
+
+void MapCall::readData(Common::SeekableReadStream &stream) {
+	stream.skip(1);
+}
+
+void MapCall::execute() {
+	_execType = kRepeating;
+	NancySceneState.requestStateChange(NancyState::kMap);
+	finishExecution();
+}
+
+void MapCallHot1Fr::readData(Common::SeekableReadStream &stream) {
+	_hotspotDesc.readData(stream);
+}
+
+void MapCallHot1Fr::execute() {
+	switch (_state) {
+	case kBegin:
+		_hotspot = _hotspotDesc.coords;
+		_state = kRun;
+		// fall through
+	case kRun:
+		if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
+			_hasHotspot = true;
+		}
+		break;
+	case kActionTrigger:
+		MapCall::execute();
+		break;
+	}
+}
+
+void MapCallHotMultiframe::readData(Common::SeekableReadStream &stream) {
+	uint16 numDescs = stream.readUint16LE();
+	_hotspots.reserve(numDescs);
+	for (uint i = 0; i < numDescs; ++i) {
+		_hotspots.push_back(HotspotDescription());
+		_hotspots[i].readData(stream);
+	}
+}
+
+void MapCallHotMultiframe::execute() {
+	switch (_state) {
+	case kBegin:
+		_state = kRun;
+		// fall through
+	case kRun:
+		_hasHotspot = false;
+		for (uint i = 0; i < _hotspots.size(); ++i) {
+			if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
+				_hasHotspot = true;
+				_hotspot = _hotspots[i].coords;
+			}
+		}
+		break;
+	case kActionTrigger:
+		MapCall::execute();
+		break;
+	}
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/navigationrecords.h b/engines/nancy/action/navigationrecords.h
new file mode 100644
index 00000000000..d02f9b1435a
--- /dev/null
+++ b/engines/nancy/action/navigationrecords.h
@@ -0,0 +1,177 @@
+/* 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_NAVIGATIONRECORDS_H
+#define NANCY_ACTION_NAVIGATIONRECORDS_H
+
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+class SceneChange : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	SceneChangeDescription _sceneChange;
+
+protected:
+	Common::String getRecordTypeName() const override { return "SceneChange"; }
+};
+
+class HotMultiframeSceneChange : public SceneChange {
+public:
+	HotMultiframeSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
+	virtual ~HotMultiframeSceneChange() {}
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	Common::Array<HotspotDescription> _hotspots;
+
+protected:
+	bool canHaveHotspot() const override { return true; }
+	Common::String getRecordTypeName() const override {
+		switch (_hoverCursor) {
+		case CursorManager::kMoveForward:
+			return "HotMultiframeForwardSceneChange";
+		case CursorManager::kMoveUp:
+			return "HotMultiframeUpSceneChange";
+		case CursorManager::kMoveDown:
+			return "HotMultiframeDownSceneChange";
+		default:
+			return "HotMultiframeSceneChange";
+		}
+	}
+
+	CursorManager::CursorType _hoverCursor;
+};
+
+class Hot1FrSceneChange : public SceneChange {
+public:
+	Hot1FrSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
+	virtual ~Hot1FrSceneChange() {}
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	
+	CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
+
+	HotspotDescription _hotspotDesc;
+
+protected:
+	bool canHaveHotspot() const override { return true; }
+	Common::String getRecordTypeName() const override {
+		switch (_hoverCursor) {
+		case CursorManager::kExit:
+			return "Hot1FrExitSceneChange";
+		case CursorManager::kMoveForward:
+			return "Hot1FrForwardSceneChange";
+		case CursorManager::kMoveBackward:
+			return "Hot1FrBackSceneChange";
+		case CursorManager::kMoveUp:
+			return "Hot1FrUpSceneChange";
+		case CursorManager::kMoveDown:
+			return "Hot1FrDownSceneChange";
+		case CursorManager::kTurnLeft:
+			return "Hot1FrLeftSceneChange";
+		case CursorManager::kTurnRight:
+			return "Hot1FrUpSceneChange";
+		default:
+			return "Hot1FrSceneChange";
+		}
+	}
+
+	CursorManager::CursorType _hoverCursor;
+};
+
+class HotMultiframeMultisceneChange : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	SceneChangeDescription _onTrue;
+	SceneChangeDescription _onFalse;
+	byte _condType;
+	uint16 _conditionID;
+	byte _conditionPayload;
+	Common::Array<HotspotDescription> _hotspots;
+
+protected:
+	bool canHaveHotspot() const override { return true; }
+	Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneChange"; }
+};
+
+class HotMultiframeMultisceneCursorTypeSceneChange : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	Common::Array<SceneChangeDescription> _scenes;
+	Common::Array<uint16> _cursorTypes;
+
+	SceneChangeDescription _defaultScene;
+	Common::Array<HotspotDescription> _hotspots;
+
+protected:
+	Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneCursorTypeSceneChange"; }
+};
+
+class MapCall : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	CursorManager::CursorType getHoverCursor() const override { return CursorManager::kExit; }
+
+protected:
+	Common::String getRecordTypeName() const override { return "MapCall"; }
+};
+
+class MapCallHot1Fr : public MapCall {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	HotspotDescription _hotspotDesc;
+
+protected:
+	bool canHaveHotspot() const override { return true; }
+	Common::String getRecordTypeName() const override { return "MapCallHot1Fr"; }
+};
+
+class MapCallHotMultiframe : public MapCall {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	Common::Array<HotspotDescription> _hotspots;
+
+protected:
+	bool canHaveHotspot() const override { return true; }
+	Common::String getRecordTypeName() const override { return "MapCallHotMultiframe"; }
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_NAVIGATIONRECORDS_H
diff --git a/engines/nancy/action/bombpuzzle.cpp b/engines/nancy/action/puzzle/bombpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/bombpuzzle.cpp
rename to engines/nancy/action/puzzle/bombpuzzle.cpp
index 3d46607b198..847a55856ab 100644
--- a/engines/nancy/action/bombpuzzle.cpp
+++ b/engines/nancy/action/puzzle/bombpuzzle.cpp
@@ -28,7 +28,7 @@
 
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/bombpuzzle.h"
+#include "engines/nancy/action/puzzle/bombpuzzle.h"
 
 namespace Nancy {
 namespace Action {
diff --git a/engines/nancy/action/bombpuzzle.h b/engines/nancy/action/puzzle/bombpuzzle.h
similarity index 100%
rename from engines/nancy/action/bombpuzzle.h
rename to engines/nancy/action/puzzle/bombpuzzle.h
diff --git a/engines/nancy/action/collisionpuzzle.cpp b/engines/nancy/action/puzzle/collisionpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/collisionpuzzle.cpp
rename to engines/nancy/action/puzzle/collisionpuzzle.cpp
index 0c41e7cb991..5955eaadf39 100644
--- a/engines/nancy/action/collisionpuzzle.cpp
+++ b/engines/nancy/action/puzzle/collisionpuzzle.cpp
@@ -28,7 +28,7 @@
 
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/collisionpuzzle.h"
+#include "engines/nancy/action/puzzle/collisionpuzzle.h"
 
 namespace Nancy {
 namespace Action {
diff --git a/engines/nancy/action/collisionpuzzle.h b/engines/nancy/action/puzzle/collisionpuzzle.h
similarity index 100%
rename from engines/nancy/action/collisionpuzzle.h
rename to engines/nancy/action/puzzle/collisionpuzzle.h
diff --git a/engines/nancy/action/leverpuzzle.cpp b/engines/nancy/action/puzzle/leverpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/leverpuzzle.cpp
rename to engines/nancy/action/puzzle/leverpuzzle.cpp
index 715ed4b3d39..7572e4a236b 100644
--- a/engines/nancy/action/leverpuzzle.cpp
+++ b/engines/nancy/action/puzzle/leverpuzzle.cpp
@@ -26,7 +26,7 @@
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/leverpuzzle.h"
+#include "engines/nancy/action/puzzle/leverpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/leverpuzzle.h b/engines/nancy/action/puzzle/leverpuzzle.h
similarity index 100%
rename from engines/nancy/action/leverpuzzle.h
rename to engines/nancy/action/puzzle/leverpuzzle.h
diff --git a/engines/nancy/action/orderingpuzzle.cpp b/engines/nancy/action/puzzle/orderingpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/orderingpuzzle.cpp
rename to engines/nancy/action/puzzle/orderingpuzzle.cpp
index 8632c650775..016869569b3 100644
--- a/engines/nancy/action/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/sound.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/orderingpuzzle.h"
+#include "engines/nancy/action/puzzle/orderingpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/orderingpuzzle.h b/engines/nancy/action/puzzle/orderingpuzzle.h
similarity index 100%
rename from engines/nancy/action/orderingpuzzle.h
rename to engines/nancy/action/puzzle/orderingpuzzle.h
diff --git a/engines/nancy/action/overridelockpuzzle.cpp b/engines/nancy/action/puzzle/overridelockpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/overridelockpuzzle.cpp
rename to engines/nancy/action/puzzle/overridelockpuzzle.cpp
index ec2ebc738b8..d89e87dbba1 100644
--- a/engines/nancy/action/overridelockpuzzle.cpp
+++ b/engines/nancy/action/puzzle/overridelockpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/state/scene.h"
 #include "engines/nancy/ui/viewport.h"
 
-#include "engines/nancy/action/overridelockpuzzle.h"
+#include "engines/nancy/action/puzzle/overridelockpuzzle.h"
 
 #include "common/random.h"
 
diff --git a/engines/nancy/action/overridelockpuzzle.h b/engines/nancy/action/puzzle/overridelockpuzzle.h
similarity index 100%
rename from engines/nancy/action/overridelockpuzzle.h
rename to engines/nancy/action/puzzle/overridelockpuzzle.h
diff --git a/engines/nancy/action/passwordpuzzle.cpp b/engines/nancy/action/puzzle/passwordpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/passwordpuzzle.cpp
rename to engines/nancy/action/puzzle/passwordpuzzle.cpp
index 774c37e028a..0cb65efb291 100644
--- a/engines/nancy/action/passwordpuzzle.cpp
+++ b/engines/nancy/action/puzzle/passwordpuzzle.cpp
@@ -25,7 +25,7 @@
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/passwordpuzzle.h"
+#include "engines/nancy/action/puzzle/passwordpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/passwordpuzzle.h b/engines/nancy/action/puzzle/passwordpuzzle.h
similarity index 100%
rename from engines/nancy/action/passwordpuzzle.h
rename to engines/nancy/action/puzzle/passwordpuzzle.h
diff --git a/engines/nancy/action/raycastpuzzle.cpp b/engines/nancy/action/puzzle/raycastpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/raycastpuzzle.cpp
rename to engines/nancy/action/puzzle/raycastpuzzle.cpp
index 8700ddd97ff..9e879d1fe36 100644
--- a/engines/nancy/action/raycastpuzzle.cpp
+++ b/engines/nancy/action/puzzle/raycastpuzzle.cpp
@@ -27,7 +27,7 @@
 #include "engines/nancy/enginedata.h"
 #include "engines/nancy/resource.h"
 
-#include "engines/nancy/action/raycastpuzzle.h"
+#include "engines/nancy/action/puzzle/raycastpuzzle.h"
 #include "engines/nancy/state/scene.h"
 
 #include "common/stack.h"
diff --git a/engines/nancy/action/raycastpuzzle.h b/engines/nancy/action/puzzle/raycastpuzzle.h
similarity index 100%
rename from engines/nancy/action/raycastpuzzle.h
rename to engines/nancy/action/puzzle/raycastpuzzle.h
diff --git a/engines/nancy/action/riddlepuzzle.cpp b/engines/nancy/action/puzzle/riddlepuzzle.cpp
similarity index 99%
rename from engines/nancy/action/riddlepuzzle.cpp
rename to engines/nancy/action/puzzle/riddlepuzzle.cpp
index ec0915305ae..fc8a24127fa 100644
--- a/engines/nancy/action/riddlepuzzle.cpp
+++ b/engines/nancy/action/puzzle/riddlepuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/puzzledata.h"
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/riddlepuzzle.h"
+#include "engines/nancy/action/puzzle/riddlepuzzle.h"
 
 #include "common/random.h"
 
diff --git a/engines/nancy/action/riddlepuzzle.h b/engines/nancy/action/puzzle/riddlepuzzle.h
similarity index 100%
rename from engines/nancy/action/riddlepuzzle.h
rename to engines/nancy/action/puzzle/riddlepuzzle.h
diff --git a/engines/nancy/action/rippedletterpuzzle.cpp b/engines/nancy/action/puzzle/rippedletterpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/rippedletterpuzzle.cpp
rename to engines/nancy/action/puzzle/rippedletterpuzzle.cpp
index ccd651897e4..a7d186b938e 100644
--- a/engines/nancy/action/rippedletterpuzzle.cpp
+++ b/engines/nancy/action/puzzle/rippedletterpuzzle.cpp
@@ -29,7 +29,7 @@
 #include "engines/nancy/puzzledata.h"
 
 #include "engines/nancy/state/scene.h"
-#include "engines/nancy/action/rippedletterpuzzle.h"
+#include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
 
 #include "graphics/transform_struct.h"
 
diff --git a/engines/nancy/action/rippedletterpuzzle.h b/engines/nancy/action/puzzle/rippedletterpuzzle.h
similarity index 100%
rename from engines/nancy/action/rippedletterpuzzle.h
rename to engines/nancy/action/puzzle/rippedletterpuzzle.h
diff --git a/engines/nancy/action/rotatinglockpuzzle.cpp b/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
similarity index 98%
rename from engines/nancy/action/rotatinglockpuzzle.cpp
rename to engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
index e2c810ca15b..83bdb17aa23 100644
--- a/engines/nancy/action/rotatinglockpuzzle.cpp
+++ b/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/rotatinglockpuzzle.h"
+#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/rotatinglockpuzzle.h b/engines/nancy/action/puzzle/rotatinglockpuzzle.h
similarity index 100%
rename from engines/nancy/action/rotatinglockpuzzle.h
rename to engines/nancy/action/puzzle/rotatinglockpuzzle.h
diff --git a/engines/nancy/action/safelockpuzzle.cpp b/engines/nancy/action/puzzle/safelockpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/safelockpuzzle.cpp
rename to engines/nancy/action/puzzle/safelockpuzzle.cpp
index bef8649e6b6..4927bb95266 100644
--- a/engines/nancy/action/safelockpuzzle.cpp
+++ b/engines/nancy/action/puzzle/safelockpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/safelockpuzzle.h"
+#include "engines/nancy/action/puzzle/safelockpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/safelockpuzzle.h b/engines/nancy/action/puzzle/safelockpuzzle.h
similarity index 100%
rename from engines/nancy/action/safelockpuzzle.h
rename to engines/nancy/action/puzzle/safelockpuzzle.h
diff --git a/engines/nancy/action/setplayerclock.cpp b/engines/nancy/action/puzzle/setplayerclock.cpp
similarity index 99%
rename from engines/nancy/action/setplayerclock.cpp
rename to engines/nancy/action/puzzle/setplayerclock.cpp
index bc11c539085..327507f861b 100644
--- a/engines/nancy/action/setplayerclock.cpp
+++ b/engines/nancy/action/puzzle/setplayerclock.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-#include "engines/nancy/action/setplayerclock.h"
+#include "engines/nancy/action/puzzle/setplayerclock.h"
 #include "engines/nancy/state/scene.h"
 #include "engines/nancy/ui/clock.h"
 
diff --git a/engines/nancy/action/setplayerclock.h b/engines/nancy/action/puzzle/setplayerclock.h
similarity index 100%
rename from engines/nancy/action/setplayerclock.h
rename to engines/nancy/action/puzzle/setplayerclock.h
diff --git a/engines/nancy/action/sliderpuzzle.cpp b/engines/nancy/action/puzzle/sliderpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/sliderpuzzle.cpp
rename to engines/nancy/action/puzzle/sliderpuzzle.cpp
index b3d11b52ac1..7d1c1bb29f3 100644
--- a/engines/nancy/action/sliderpuzzle.cpp
+++ b/engines/nancy/action/puzzle/sliderpuzzle.cpp
@@ -27,7 +27,7 @@
 #include "engines/nancy/util.h"
 #include "engines/nancy/puzzledata.h"
 
-#include "engines/nancy/action/sliderpuzzle.h"
+#include "engines/nancy/action/puzzle/sliderpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/sliderpuzzle.h b/engines/nancy/action/puzzle/sliderpuzzle.h
similarity index 100%
rename from engines/nancy/action/sliderpuzzle.h
rename to engines/nancy/action/puzzle/sliderpuzzle.h
diff --git a/engines/nancy/action/soundequalizerpuzzle.cpp b/engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/soundequalizerpuzzle.cpp
rename to engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
index d58cb0c456e..63211dc7800 100644
--- a/engines/nancy/action/soundequalizerpuzzle.cpp
+++ b/engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
@@ -19,7 +19,7 @@
  *
  */
 
-#include "engines/nancy/action/soundequalizerpuzzle.h"
+#include "engines/nancy/action/puzzle/soundequalizerpuzzle.h"
 #include "engines/nancy/state/scene.h"
 #include "engines/nancy/ui/scrollbar.h"
 
diff --git a/engines/nancy/action/soundequalizerpuzzle.h b/engines/nancy/action/puzzle/soundequalizerpuzzle.h
similarity index 100%
rename from engines/nancy/action/soundequalizerpuzzle.h
rename to engines/nancy/action/puzzle/soundequalizerpuzzle.h
diff --git a/engines/nancy/action/tangrampuzzle.cpp b/engines/nancy/action/puzzle/tangrampuzzle.cpp
similarity index 99%
rename from engines/nancy/action/tangrampuzzle.cpp
rename to engines/nancy/action/puzzle/tangrampuzzle.cpp
index e58efbf01e8..da92220b81c 100644
--- a/engines/nancy/action/tangrampuzzle.cpp
+++ b/engines/nancy/action/puzzle/tangrampuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/puzzledata.h"
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/tangrampuzzle.h"
+#include "engines/nancy/action/puzzle/tangrampuzzle.h"
 
 namespace Nancy {
 namespace Action {
diff --git a/engines/nancy/action/tangrampuzzle.h b/engines/nancy/action/puzzle/tangrampuzzle.h
similarity index 100%
rename from engines/nancy/action/tangrampuzzle.h
rename to engines/nancy/action/puzzle/tangrampuzzle.h
diff --git a/engines/nancy/action/telephone.cpp b/engines/nancy/action/puzzle/telephone.cpp
similarity index 99%
rename from engines/nancy/action/telephone.cpp
rename to engines/nancy/action/puzzle/telephone.cpp
index 97026c93c89..c236c250ab0 100644
--- a/engines/nancy/action/telephone.cpp
+++ b/engines/nancy/action/puzzle/telephone.cpp
@@ -26,7 +26,7 @@
 #include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
-#include "engines/nancy/action/telephone.h"
+#include "engines/nancy/action/puzzle/telephone.h"
 
 #include "engines/nancy/state/scene.h"
 
diff --git a/engines/nancy/action/telephone.h b/engines/nancy/action/puzzle/telephone.h
similarity index 100%
rename from engines/nancy/action/telephone.h
rename to engines/nancy/action/puzzle/telephone.h
diff --git a/engines/nancy/action/towerpuzzle.cpp b/engines/nancy/action/puzzle/towerpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/towerpuzzle.cpp
rename to engines/nancy/action/puzzle/towerpuzzle.cpp
index 095aba6b861..42973f20387 100644
--- a/engines/nancy/action/towerpuzzle.cpp
+++ b/engines/nancy/action/puzzle/towerpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/puzzledata.h"
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/towerpuzzle.h"
+#include "engines/nancy/action/puzzle/towerpuzzle.h"
 
 namespace Nancy {
 namespace Action {
diff --git a/engines/nancy/action/towerpuzzle.h b/engines/nancy/action/puzzle/towerpuzzle.h
similarity index 100%
rename from engines/nancy/action/towerpuzzle.h
rename to engines/nancy/action/puzzle/towerpuzzle.h
diff --git a/engines/nancy/action/turningpuzzle.cpp b/engines/nancy/action/puzzle/turningpuzzle.cpp
similarity index 99%
rename from engines/nancy/action/turningpuzzle.cpp
rename to engines/nancy/action/puzzle/turningpuzzle.cpp
index 272c674dfdb..727187eaca9 100644
--- a/engines/nancy/action/turningpuzzle.cpp
+++ b/engines/nancy/action/puzzle/turningpuzzle.cpp
@@ -28,7 +28,7 @@
 #include "engines/nancy/puzzledata.h"
 #include "engines/nancy/state/scene.h"
 
-#include "engines/nancy/action/turningpuzzle.h"
+#include "engines/nancy/action/puzzle/turningpuzzle.h"
 
 namespace Nancy {
 namespace Action {
diff --git a/engines/nancy/action/turningpuzzle.h b/engines/nancy/action/puzzle/turningpuzzle.h
similarity index 100%
rename from engines/nancy/action/turningpuzzle.h
rename to engines/nancy/action/puzzle/turningpuzzle.h
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
new file mode 100644
index 00000000000..36717fa9ffc
--- /dev/null
+++ b/engines/nancy/action/soundrecords.cpp
@@ -0,0 +1,169 @@
+/* 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/util.h"
+
+#include "engines/nancy/action/soundrecords.h"
+
+#include "engines/nancy/state/scene.h"
+
+namespace Nancy {
+namespace Action {
+
+void PlayDigiSoundAndDie::readData(Common::SeekableReadStream &stream) {
+	_sound.readDIGI(stream);
+
+	if (g_nancy->getGameType() >= kGameTypeNancy3) {
+		_soundEffect = new SoundEffectDescription;
+		_soundEffect->readData(stream);
+	}
+
+	_sceneChange.readData(stream, g_nancy->getGameType() == kGameTypeVampire);
+
+	_flagOnTrigger.label = stream.readSint16LE();
+	_flagOnTrigger.flag = stream.readByte();
+	stream.skip(2);
+}
+
+void PlayDigiSoundAndDie::execute() {
+	switch (_state) {
+	case kBegin:
+		g_nancy->_sound->loadSound(_sound, &_soundEffect);
+		g_nancy->_sound->playSound(_sound);
+		_state = kRun;
+		break;
+	case kRun:
+		if (!g_nancy->_sound->isSoundPlaying(_sound)) {
+			_state = kActionTrigger;
+		}
+
+		break;
+	case kActionTrigger:
+		if (_sceneChange.sceneID != 9999) {
+			NancySceneState.changeScene(_sceneChange);
+		}
+
+		NancySceneState.setEventFlag(_flagOnTrigger);
+		g_nancy->_sound->stopSound(_sound);
+
+		finishExecution();
+		break;
+	}
+}
+
+void PlayDigiSoundCC::readData(Common::SeekableReadStream &stream) {
+	PlayDigiSoundAndDie::readData(stream);
+
+	uint16 textSize = stream.readUint16LE();
+	if (textSize) {
+		char *strBuf = new char[textSize];
+		stream.read(strBuf, textSize);
+		UI::Textbox::assembleTextLine(strBuf, _ccText, textSize);
+		delete[] strBuf;
+	}
+}
+
+void PlayDigiSoundCC::execute() {
+	if (_state == kBegin) {
+		NancySceneState.getTextbox().clear();
+		NancySceneState.getTextbox().addTextLine(_ccText);
+	}
+	PlayDigiSoundAndDie::execute();
+}
+
+void PlaySoundPanFrameAnchorAndDie::readData(Common::SeekableReadStream &stream) {
+	_sound.readDIGI(stream);
+	stream.skip(2);
+	_sound.isPanning = true;
+}
+
+void PlaySoundPanFrameAnchorAndDie::execute() {
+	g_nancy->_sound->loadSound(_sound);
+	g_nancy->_sound->playSound(_sound);
+	_isDone = true;
+}
+
+void PlaySoundMultiHS::readData(Common::SeekableReadStream &stream) {
+	_sound.readNormal(stream);
+
+	if (g_nancy->getGameType() != kGameTypeVampire) {
+		_sceneChange.readData(stream);
+		_flag.label = stream.readSint16LE();
+		_flag.flag = stream.readByte();
+		stream.skip(2);
+	} else {
+		_flag.label = kEvNoEvent;
+		_sceneChange.sceneID = 9999;
+	}
+
+	uint16 numHotspots = stream.readUint16LE();
+
+	_hotspots.reserve(numHotspots);
+	for (uint i = 0; i < numHotspots; ++i) {
+		_hotspots.push_back(HotspotDescription());
+		_hotspots.back().frameID = stream.readUint16LE();
+		readRect(stream, _hotspots.back().coords);
+	}
+}
+
+void PlaySoundMultiHS::execute() {
+	switch (_state) {
+	case kBegin:
+		_state = kRun;
+		// fall through
+	case kRun: {
+		_hasHotspot = false;
+		uint currentFrame = NancySceneState.getSceneInfo().frameID;
+
+		for (uint i = 0; i < _hotspots.size(); ++i) {
+			if (_hotspots[i].frameID == currentFrame) {
+				_hotspot = _hotspots[i].coords;
+				_hasHotspot = true;
+				break;
+			}
+		}
+
+		break;
+	}
+	case kActionTrigger:
+		g_nancy->_sound->loadSound(_sound);
+		g_nancy->_sound->playSound(_sound);
+		NancySceneState.changeScene(_sceneChange);
+		NancySceneState.setEventFlag(_flag);
+		finishExecution();
+		break;
+	}
+}
+
+void StopSound::readData(Common::SeekableReadStream &stream) {
+	_channelID = stream.readUint16LE();
+	_sceneChange.readData(stream);
+}
+
+void StopSound::execute() {
+	g_nancy->_sound->stopSound(_channelID);
+	_sceneChange.execute();
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
new file mode 100644
index 00000000000..ae4fccd2db4
--- /dev/null
+++ b/engines/nancy/action/soundrecords.h
@@ -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_SOUNDRECORDS_H
+#define NANCY_ACTION_SOUNDRECORDS_H
+
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+class PlayDigiSoundAndDie : public ActionRecord {
+public:
+	PlayDigiSoundAndDie() {}
+	~PlayDigiSoundAndDie() { delete _soundEffect; }
+	
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	SoundDescription _sound;
+	SoundEffectDescription *_soundEffect = nullptr;
+	SceneChangeDescription _sceneChange;
+	FlagDescription _flagOnTrigger;
+
+protected:
+	Common::String getRecordTypeName() const override { return "PlayDigiSoundAndDie"; }
+};
+
+class PlayDigiSoundCC : public PlayDigiSoundAndDie {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	Common::String _ccText;
+
+protected:
+	Common::String getRecordTypeName() const override { return "PlayDigiSoundCC"; }
+};
+
+class PlaySoundPanFrameAnchorAndDie : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	SoundDescription _sound;
+
+protected:
+	Common::String getRecordTypeName() const override { return "PlaySoundPanFrameAnchorAndDie"; }
+};
+
+class PlaySoundMultiHS : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	SoundDescription _sound; // 0x0
+	SceneChangeDescription _sceneChange; // 0x22
+	FlagDescription _flag; // 0x2A
+	Common::Array<HotspotDescription> _hotspots; // 0x31
+
+protected:
+	Common::String getRecordTypeName() const override { return "PlaySoundMultiHS"; }
+};
+
+class StopSound : public ActionRecord {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	uint _channelID;
+	SceneChangeWithFlag _sceneChange;
+
+protected:
+	Common::String getRecordTypeName() const override { return "StopSound"; }
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_NAVIGATIONRECORDS_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index c060ec07165..3ae79ad5359 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -4,29 +4,31 @@ MODULE_OBJS = \
   action/actionmanager.o \
   action/actionrecord.o \
   action/arfactory.o \
-  action/bombpuzzle.o \
-  action/collisionpuzzle.o \
+  action/navigationrecords.o \
+  action/soundrecords.o \
+  action/miscrecords.o \
   action/conversation.o \
-  action/leverpuzzle.o \
-  action/orderingpuzzle.o \
   action/overlay.o \
-  action/overridelockpuzzle.o \
-  action/passwordpuzzle.o \
-  action/raycastpuzzle.o \
-  action/recordtypes.o \
-  action/riddlepuzzle.o \
-  action/rippedletterpuzzle.o \
-  action/rotatinglockpuzzle.o \
-  action/safelockpuzzle.o \
   action/secondarymovie.o \
   action/secondaryvideo.o \
-  action/setplayerclock.o \
-  action/sliderpuzzle.o \
-  action/soundequalizerpuzzle.o \
-  action/tangrampuzzle.o \
-  action/towerpuzzle.o \
-  action/turningpuzzle.o \
-  action/telephone.o \
+  action/puzzle/bombpuzzle.o \
+  action/puzzle/collisionpuzzle.o \
+  action/puzzle/leverpuzzle.o \
+  action/puzzle/orderingpuzzle.o \
+  action/puzzle/overridelockpuzzle.o \
+  action/puzzle/passwordpuzzle.o \
+  action/puzzle/raycastpuzzle.o \
+  action/puzzle/riddlepuzzle.o \
+  action/puzzle/rippedletterpuzzle.o \
+  action/puzzle/rotatinglockpuzzle.o \
+  action/puzzle/safelockpuzzle.o \
+  action/puzzle/setplayerclock.o \
+  action/puzzle/sliderpuzzle.o \
+  action/puzzle/soundequalizerpuzzle.o \
+  action/puzzle/tangrampuzzle.o \
+  action/puzzle/telephone.o \
+  action/puzzle/towerpuzzle.o \
+  action/puzzle/turningpuzzle.o \
   ui/fullscreenimage.o \
   ui/animatedbutton.o \
   ui/button.o \


Commit: 2ee8b2cea6ef3b0af00d0cf0b7f92df134df82dd
    https://github.com/scummvm/scummvm/commit/2ee8b2cea6ef3b0af00d0cf0b7f92df134df82dd
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Implement MazeChasePuzzle

Added a new action record class implementing a specific
puzzle in nancy5 where a player piece is being chased by
several enemy pieces on a grid.

Changed paths:
  A engines/nancy/action/puzzle/mazechasepuzzle.cpp
  A engines/nancy/action/puzzle/mazechasepuzzle.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 4178a12cba5..cd41dc81b2e 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -31,6 +31,7 @@
 #include "engines/nancy/action/puzzle/bombpuzzle.h"
 #include "engines/nancy/action/puzzle/collisionpuzzle.h"
 #include "engines/nancy/action/puzzle/leverpuzzle.h"
+#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
 #include "engines/nancy/action/puzzle/orderingpuzzle.h"
 #include "engines/nancy/action/puzzle/overridelockpuzzle.h"
 #include "engines/nancy/action/puzzle/passwordpuzzle.h"
@@ -223,6 +224,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new CollisionPuzzle(CollisionPuzzle::PuzzleType::kTileMove);
 	case 214:
 		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kKeypad);
+	case 215:
+		return new MazeChasePuzzle();
 	default:
 		error("Action Record type %i is invalid!", type);
 		return nullptr;
diff --git a/engines/nancy/action/puzzle/mazechasepuzzle.cpp b/engines/nancy/action/puzzle/mazechasepuzzle.cpp
new file mode 100644
index 00000000000..5a701dedd80
--- /dev/null
+++ b/engines/nancy/action/puzzle/mazechasepuzzle.cpp
@@ -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->_graphicsManager->getInputPixelFormat());
+	_drawSurface.clear(g_nancy->_graphicsManager->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->_cursorManager->setCursorType(g_nancy->_cursorManager->_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->_cursorManager->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->_cursorManager->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->_cursorManager->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->_cursorManager->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->_cursorManager->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
diff --git a/engines/nancy/action/puzzle/mazechasepuzzle.h b/engines/nancy/action/puzzle/mazechasepuzzle.h
new file mode 100644
index 00000000000..5d632e246f8
--- /dev/null
+++ b/engines/nancy/action/puzzle/mazechasepuzzle.h
@@ -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::String _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;
+	uint16 _framesPerMove;
+
+	SoundDescription _failSound;
+	SoundDescription _moveSound;
+
+	SceneChangeWithFlag _solveScene;
+	uint16 _solveSoundDelay;
+	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
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 3ae79ad5359..ebf8eea339f 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -14,6 +14,7 @@ MODULE_OBJS = \
   action/puzzle/bombpuzzle.o \
   action/puzzle/collisionpuzzle.o \
   action/puzzle/leverpuzzle.o \
+  action/puzzle/mazechasepuzzle.o \
   action/puzzle/orderingpuzzle.o \
   action/puzzle/overridelockpuzzle.o \
   action/puzzle/passwordpuzzle.o \


Commit: 6f0e74c620a53c90bc0a9535f761dc7e7b77f698
    https://github.com/scummvm/scummvm/commit/6f0e74c620a53c90bc0a9535f761dc7e7b77f698
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-12T19:43:30+03:00

Commit Message:
NANCY: Play correct sfx when getting/losing item

Changed paths:
    engines/nancy/state/scene.cpp
    engines/nancy/ui/inventorybox.cpp


diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 8ca5120c5e8..be548732fb9 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -299,6 +299,8 @@ void Scene::addItemToInventory(uint16 id) {
 	if (_flags.heldItem == id) {
 		setHeldItem(-1);
 	}
+	
+	g_nancy->_sound->playSound("BUOK");
 
 	_inventoryBox.addItem(id);
 }
@@ -309,6 +311,8 @@ void Scene::removeItemFromInventory(uint16 id, bool pickUp) {
 	if (pickUp) {
 		setHeldItem(id);
 	}
+	
+	g_nancy->_sound->playSound("BUOK");
 
 	_inventoryBox.removeItem(id);
 }
diff --git a/engines/nancy/ui/inventorybox.cpp b/engines/nancy/ui/inventorybox.cpp
index 810ecd3e4f5..a0324a7f215 100644
--- a/engines/nancy/ui/inventorybox.cpp
+++ b/engines/nancy/ui/inventorybox.cpp
@@ -125,7 +125,6 @@ void InventoryBox::handleInput(NancyInput &input) {
 					NancySceneState.removeItemFromInventory(_itemHotspots[i].itemID);
 					_highlightedHotspot = -1;
 					hoveredHotspot = -1;
-					g_nancy->_sound->playSound("GLOB");
 				}
 			}
 			break;




More information about the Scummvm-git-logs mailing list