[Scummvm-git-logs] scummvm master -> 471bcccfe423d85cc98c86f666b2383536cdc0f3

fracturehill noreply at scummvm.org
Sun Apr 16 12:46:31 UTC 2023


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

Summary:
7dbfa1e91a NANCY: Overlay fixes
4d9bde82dd NANCY: Avoid cursor flashing when changing scene
0d3d384c1a NANCY: Separate game-specific data in savefiles
970d6c961e NANCY: Do not autosave when scene is not running
471bcccfe4 NANCY: Implement RippedLetterPuzzle


Commit: 7dbfa1e91a7e88e4bbc3a42e22145560338a1232
    https://github.com/scummvm/scummvm/commit/7dbfa1e91a7e88e4bbc3a42e22145560338a1232
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-16T15:40:50+03:00

Commit Message:
NANCY: Overlay fixes

Added code to make sure sound is stopped when an
Overlay is triggered. Added code ensuring hotspots
are enabled when in animated mode. Moved the
setVisible() call so the overlay gets hidden when clicked.

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


diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index bdbb238e018..52d5714a175 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -120,14 +120,6 @@ void Overlay::execute() {
 						!g_nancy->_sound->isSoundPlaying(_sound))	) {
 
 				_state = kActionTrigger;
-
-				// Not sure if hiding when triggered is a hack or the intended behavior, but it's here to fix
-				// nancy1's safe lock light not turning off.
-				setVisible(false);
-
-				if (!g_nancy->_sound->isSoundPlaying(_sound)) {
-					g_nancy->_sound->stopSound(_sound);
-				}
 			} else {
 				// Check if we've moved the viewport
 				uint16 newFrame = NancySceneState.getSceneInfo().frameID;
@@ -136,11 +128,18 @@ void Overlay::execute() {
 					_currentViewportFrame = newFrame;
 
 					setVisible(false);
+					_hasHotspot = false;
 
 					for (uint i = 0; i < _bitmaps.size(); ++i) {
 						if (_currentViewportFrame == _bitmaps[i].frameID) {
 							moveTo(_bitmaps[i].dest);
 							setVisible(true);
+
+							if (_enableHotspot == kPlayOverlayWithHotspot) {
+								_hotspot = _screenPosition;
+								_hasHotspot = true;
+							}
+
 							break;
 						}
 					}
@@ -179,7 +178,7 @@ void Overlay::execute() {
 						if (_overlayType == kPlayOverlayStatic) {
 							setFrame(i);
 
-							if (_enableHotspot) {
+							if (_enableHotspot == kPlayOverlayWithHotspot) {
 								_hotspot = _screenPosition;
 								_hasHotspot = true;
 							}
@@ -194,11 +193,15 @@ void Overlay::execute() {
 		break;
 	}
 	case kActionTrigger:
+		setVisible(false);
+		g_nancy->_sound->stopSound(_sound);
+
 		_flagsOnTrigger.execute();
 		if (_hasSceneChange == kPlayOverlaySceneChange) {
 			NancySceneState.changeScene(_sceneChange);
 			finishExecution();
 		}
+
 		break;
 	}
 }


Commit: 4d9bde82ddb747a437f6867a4e3d7af930759ccf
    https://github.com/scummvm/scummvm/commit/4d9bde82ddb747a437f6867a4e3d7af930759ccf
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-16T15:40:50+03:00

Commit Message:
NANCY: Avoid cursor flashing when changing scene

Added code to suppress draw calls and cursor changes.
This makes it so there are no single-frame discrepancies
between when the cursor is changed and when the scene is.

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


diff --git a/engines/nancy/cursor.cpp b/engines/nancy/cursor.cpp
index 78ec5fe60a0..f7ce0eec1b9 100644
--- a/engines/nancy/cursor.cpp
+++ b/engines/nancy/cursor.cpp
@@ -74,7 +74,7 @@ void CursorManager::setCursor(CursorType type, int16 itemID) {
 		_curItemID = itemID;
 	}
 
-	bool hasItem = false;
+	_hasItem = false;
 
 	switch (type) {
 	case kNormalArrow:
@@ -107,18 +107,28 @@ void CursorManager::setCursor(CursorType type, int16 itemID) {
 		} else {
 			// Item held
 			itemsOffset = g_nancy->getStaticData().numNonItemCursors;
-			hasItem = true;
+			_hasItem = true;
 		}
 
 		_curCursorID = itemID * (gameType <= kGameTypeNancy1? 4 : 5) + itemsOffset + type;
 	}
 	}
+}
+
+void CursorManager::setCursorType(CursorType type) {
+	setCursor(type, _curItemID);
+}
+
+void CursorManager::setCursorItemID(int16 itemID) {
+	setCursor(_curCursorType, itemID);
+}
 
+void CursorManager::applyCursor() {
 	Graphics::ManagedSurface *surf;
 	Common::Rect bounds = _cursors[_curCursorID].bounds;
 	Common::Point hotspot = _cursors[_curCursorID].hotspot;
 
-	if (hasItem) {
+	if (_hasItem) {
 		surf = &_invCursorsSurface;
 
 	} else {
@@ -147,14 +157,6 @@ void CursorManager::setCursor(CursorType type, int16 itemID) {
 	CursorMan.replaceCursor(temp.getPixels(), temp.w, temp.h, hotspot.x, hotspot.y, transColor, false, &temp.format);
 }
 
-void CursorManager::setCursorType(CursorType type) {
-	setCursor(type, _curItemID);
-}
-
-void CursorManager::setCursorItemID(int16 itemID) {
-	setCursor(_curCursorType, itemID);
-}
-
 void CursorManager::showCursor(bool shouldShow) {
 	CursorMan.showMouse(shouldShow);
 }
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index 4cbb3578afe..c7122cf1c61 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -40,14 +40,19 @@ public:
 		_isInitialized(false),
 		_curItemID(-1),
 		_curCursorType(kNormal),
-		_curCursorID(0) {}
+		_curCursorID(0),
+		_hasItem(false) {}
 
 	void init(Common::SeekableReadStream *chunkStream);
 
+	// Change the current cursor ID. Does not change the graphic
 	void setCursor(CursorType type, int16 itemID);
 	void setCursorType(CursorType type);
 	void setCursorItemID(int16 itemID);
 
+	// Change the cursor graphic. Should be called right before drawing to screen
+	void applyCursor();
+
 	const Common::Point &getCurrentCursorHotspot() { return _cursors[_curCursorID].hotspot;}
 	const Common::Rect &getPrimaryVideoInactiveZone() { return _primaryVideoInactiveZone; }
 	const Common::Point &getPrimaryVideoInitialPos() { return _primaryVideoInitialPos; }
@@ -71,6 +76,7 @@ private:
 	CursorType _curCursorType;
 	int16 _curItemID;
 	uint _curCursorID;
+	bool _hasItem;
 	bool _isInitialized;
 };
 
diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index c8822d5cf65..639501eedcf 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -27,6 +27,7 @@
 #include "engines/nancy/graphics.h"
 #include "engines/nancy/renderobject.h"
 #include "engines/nancy/resource.h"
+#include "engines/nancy/cursor.h"
 #include "engines/nancy/state/scene.h"
 
 namespace Nancy {
@@ -35,7 +36,8 @@ GraphicsManager::GraphicsManager() :
 	_objects(objectComparator),
 	_inputPixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0),
 	_screenPixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0),
-	_clut8Format(Graphics::PixelFormat::createFormatCLUT8()) {}
+	_clut8Format(Graphics::PixelFormat::createFormatCLUT8()),
+	_isSuppressed(false) {}
 
 void GraphicsManager::init() {
 	initGraphics(640, 480, &_screenPixelFormat);
@@ -47,6 +49,12 @@ void GraphicsManager::init() {
 }
 
 void GraphicsManager::draw() {
+	if (_isSuppressed) {
+		_isSuppressed = false;
+		return;
+	}
+
+	g_nancy->_cursorManager->applyCursor();
 	Common::List<Common::Rect> dirtyRects;
 
 	// Update graphics for all RenderObjects and determine
@@ -149,6 +157,10 @@ void GraphicsManager::redrawAll() {
 	}
 }
 
+void GraphicsManager::suppressNextDraw() {
+	_isSuppressed = true;
+}
+
 void GraphicsManager::loadSurfacePalette(Graphics::ManagedSurface &inSurf, const Common::String paletteFilename, uint paletteStart, uint paletteSize) {
 	Common::File f;
 	if (f.open(paletteFilename + ".bmp")) {
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index 8dbebb700c0..c6d11730f00 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -46,6 +46,7 @@ public:
 	void clearObjects();
 
 	void redrawAll();
+	void suppressNextDraw();
 
 	const Font *getFont(uint id) const { return id < _fonts.size() ? &_fonts[id] : nullptr; }
 	const Graphics::Screen *getScreen() { return &_screen; }
@@ -79,6 +80,8 @@ private:
 
 	Graphics::Screen _screen;
 	Common::Array<Font> _fonts;
+
+	bool _isSuppressed;
 };
 
 } // End of namespace Nancy
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index b2c4c430fcd..8cdbce97b83 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -658,6 +658,10 @@ void Scene::run() {
 	if (_lightning) {
 		_lightning->run();
 	}
+
+	if (_state == kLoad) {
+		g_nancy->_graphicsManager->suppressNextDraw();
+	}
 }
 
 void Scene::handleInput() {


Commit: 0d3d384c1a5625c4aa434129c48faff3a980734b
    https://github.com/scummvm/scummvm/commit/0d3d384c1a5625c4aa434129c48faff3a980734b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-16T15:40:50+03:00

Commit Message:
NANCY: Separate game-specific data in savefiles

Added a check when saving/loading to remove
unnecessary puzzle data in games with no instances of
a specific puzzle type.

Changed paths:
    engines/nancy/action/sliderpuzzle.cpp
    engines/nancy/action/sliderpuzzle.h
    engines/nancy/commontypes.h
    engines/nancy/state/scene.cpp
    engines/nancy/state/scene.h


diff --git a/engines/nancy/action/sliderpuzzle.cpp b/engines/nancy/action/sliderpuzzle.cpp
index 208ddc7e698..3f4b5ce55cf 100644
--- a/engines/nancy/action/sliderpuzzle.cpp
+++ b/engines/nancy/action/sliderpuzzle.cpp
@@ -46,6 +46,9 @@ void SliderPuzzle::readData(Common::SeekableReadStream &stream) {
 	_spuzData = g_nancy->_sliderPuzzleData;
 	assert(_spuzData);
 
+	_puzzleState = NancySceneState._sliderPuzzleState;
+	assert(_puzzleState);
+
 	readFilename(stream, _imageName);
 
 	_width = stream.readUint16LE();
@@ -118,22 +121,22 @@ void SliderPuzzle::execute() {
 	case kBegin:
 		init();
 		registerGraphics();
-		if (!NancySceneState._sliderPuzzleState.playerHasTriedPuzzle) {
-			NancySceneState._sliderPuzzleState.playerTileOrder.clear();
-			NancySceneState._sliderPuzzleState.playerTileOrder.resize(_height);
+		if (!_puzzleState->playerHasTriedPuzzle) {
+			_puzzleState->playerTileOrder.clear();
+			_puzzleState->playerTileOrder.resize(_height);
 			for (uint y = 0; y < _height; ++y) {
-				NancySceneState._sliderPuzzleState.playerTileOrder[y].resize(_width);
+				_puzzleState->playerTileOrder[y].resize(_width);
 				for (uint x = 0; x < _width; ++x) {
-					NancySceneState._sliderPuzzleState.playerTileOrder[y][x] = _spuzData->tileOrder[NancySceneState.getDifficulty()][y * 6 + x];
+					_puzzleState->playerTileOrder[y][x] = _spuzData->tileOrder[NancySceneState.getDifficulty()][y * 6 + x];
 				}
 			}
 
-			NancySceneState._sliderPuzzleState.playerHasTriedPuzzle = true;
+			_puzzleState->playerHasTriedPuzzle = true;
 		}
 
 		for (uint y = 0; y < _height; ++y) {
 			for (uint x = 0; x < _width; ++x) {
-				drawTile(NancySceneState._sliderPuzzleState.playerTileOrder[y][x], x, y);
+				drawTile(_puzzleState->playerTileOrder[y][x], x, y);
 			}
 		}
 
@@ -145,7 +148,7 @@ void SliderPuzzle::execute() {
 		case kNotSolved:
 			for (uint y = 0; y < _height; ++y) {
 				for (uint x = 0; x < _width; ++x) {
-					if (NancySceneState._sliderPuzzleState.playerTileOrder[y][x] != _correctTileOrder[y][x]) {
+					if (_puzzleState->playerTileOrder[y][x] != _correctTileOrder[y][x]) {
 						return;
 					}
 				}
@@ -174,7 +177,7 @@ void SliderPuzzle::execute() {
 		case kWaitForSound:
 			NancySceneState.changeScene(_solveExitScene);
 			NancySceneState.setEventFlag(_flagOnSolve);
-			NancySceneState._sliderPuzzleState.playerHasTriedPuzzle = false;
+			_puzzleState->playerHasTriedPuzzle = false;
 			break;
 		}
 
@@ -204,7 +207,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 	for (uint y = 0; y < _height; ++y) {
 		bool shouldBreak = false;
 		for (uint x = 0; x < _width; ++x) {
-			if (x > 0 && NancySceneState._sliderPuzzleState.playerTileOrder[y][x - 1] < 0) {
+			if (x > 0 && _puzzleState->playerTileOrder[y][x - 1] < 0) {
 				if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
 					currentTileX = x;
 					currentTileY = y;
@@ -212,7 +215,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 					shouldBreak = true;
 					break;
 				}
-			} else if ((int)x < _width - 1 && NancySceneState._sliderPuzzleState.playerTileOrder[y][x + 1] < 0) {
+			} else if ((int)x < _width - 1 && _puzzleState->playerTileOrder[y][x + 1] < 0) {
 				if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
 					currentTileX = x;
 					currentTileY = y;
@@ -220,7 +223,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 					shouldBreak = true;
 					break;
 				}
-			} else if (y > 0 && NancySceneState._sliderPuzzleState.playerTileOrder[y - 1][x] < 0) {
+			} else if (y > 0 && _puzzleState->playerTileOrder[y - 1][x] < 0) {
 				if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
 					currentTileX = x;
 					currentTileY = y;
@@ -228,7 +231,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 					shouldBreak = true;
 					break;
 				}
-			} else if ((int)y < _height - 1 && NancySceneState._sliderPuzzleState.playerTileOrder[y + 1][x] < 0) {
+			} else if ((int)y < _height - 1 && _puzzleState->playerTileOrder[y + 1][x] < 0) {
 				if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
 					currentTileX = x;
 					currentTileY = y;
@@ -251,35 +254,35 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 			g_nancy->_sound->playSound(_clickSound);
 			switch (direction) {
 			case kUp: {
-				uint curTileID = NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX];
+				uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
 				drawTile(curTileID, currentTileX, currentTileY - 1);
 				undrawTile(currentTileX, currentTileY);
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY - 1][currentTileX] = curTileID;
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX] = -10;
+				_puzzleState->playerTileOrder[currentTileY - 1][currentTileX] = curTileID;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
 				break;
 			}
 			case kDown: {
-				uint curTileID = NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX];
+				uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
 				drawTile(curTileID, currentTileX, currentTileY + 1);
 				undrawTile(currentTileX, currentTileY);
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY + 1][currentTileX] = curTileID;
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX] = -10;
+				_puzzleState->playerTileOrder[currentTileY + 1][currentTileX] = curTileID;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
 				break;
 			}
 			case kLeft: {
-				uint curTileID = NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX];
+				uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
 				drawTile(curTileID, currentTileX - 1, currentTileY);
 				undrawTile(currentTileX, currentTileY);
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX - 1] = curTileID;
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX] = -10;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX - 1] = curTileID;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
 				break;
 			}
 			case kRight: {
-				uint curTileID = NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX];
+				uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
 				drawTile(curTileID, currentTileX + 1, currentTileY);
 				undrawTile(currentTileX, currentTileY);
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX + 1] = curTileID;
-				NancySceneState._sliderPuzzleState.playerTileOrder[currentTileY][currentTileX] = -10;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX + 1] = curTileID;
+				_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
 				break;
 			}
 			}
diff --git a/engines/nancy/action/sliderpuzzle.h b/engines/nancy/action/sliderpuzzle.h
index ffd64f8e653..b38bc1900c1 100644
--- a/engines/nancy/action/sliderpuzzle.h
+++ b/engines/nancy/action/sliderpuzzle.h
@@ -49,6 +49,7 @@ public:
 	void onPause(bool pause) override;
 
 	SPUZ *_spuzData = nullptr;
+	SliderPuzzleState *_puzzleState = nullptr;
 
 	Common::String _imageName; // 0x00
 	uint16 _width = 0; // 0xA
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index a4194503dda..5c62c4f8d18 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -240,6 +240,12 @@ struct StaticData {
 	void readData(Common::SeekableReadStream &stream, Common::Language language);
 };
 
+// Structs for game-specific puzzle data that needs to be saved/loaded
+struct SliderPuzzleState {
+	Common::Array<Common::Array<int16>> playerTileOrder;
+	bool playerHasTriedPuzzle;
+};
+
 } // End of namespace Nancy
 
 #endif // NANCY_COMMONYPES_H
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 8cdbce97b83..51dd8baadb6 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -113,7 +113,8 @@ Scene::Scene() :
 		_actionManager(),
 		_difficulty(0),
 		_activeConversation(nullptr),
-		_lightning(nullptr) {}
+		_lightning(nullptr),
+		_sliderPuzzleState(nullptr) {}
 
 Scene::~Scene()  {
 	delete _helpButton;
@@ -123,6 +124,7 @@ Scene::~Scene()  {
 	delete _inventoryBoxOrnaments;
 	delete _clock;
 	delete _lightning;
+	delete _sliderPuzzleState;
 }
 
 void Scene::process() {
@@ -462,28 +464,43 @@ void Scene::synchronize(Common::Serializer &ser) {
 	ser.syncAsSint16LE(_lastHintCharacter);
 	ser.syncAsSint16LE(_lastHintID);
 
-	// Synchronize SliderPuzzle static data
-	ser.syncAsByte(_sliderPuzzleState.playerHasTriedPuzzle);
+	switch (g_nancy->getGameType()) {
+		case kGameTypeVampire:
+			// Fall through to avoid having to bump the savegame version
+			// fall through
+		case kGameTypeNancy1: {
+			// Synchronize SliderPuzzle static data
+			if (!_sliderPuzzleState) {
+				return;
+			}
 
-	byte x = 0, y = 0;
+			ser.syncAsByte(_sliderPuzzleState->playerHasTriedPuzzle);
 
-	if (ser.isSaving()) {
-		y = _sliderPuzzleState.playerTileOrder.size();
-		if (y) {
-			x = _sliderPuzzleState.playerTileOrder.back().size();
-		} else {
-			x = 0;
-		}
-	}
+			byte x = 0, y = 0;
 
-	ser.syncAsByte(x);
-	ser.syncAsByte(y);
+			if (ser.isSaving()) {
+				y = _sliderPuzzleState->playerTileOrder.size();
+				if (y) {
+					x = _sliderPuzzleState->playerTileOrder.back().size();
+				} else {
+					x = 0;
+				}
+			}
 
-	_sliderPuzzleState.playerTileOrder.resize(y);
+			ser.syncAsByte(x);
+			ser.syncAsByte(y);
 
-	for (int i = 0; i < y; ++i) {
-		_sliderPuzzleState.playerTileOrder[i].resize(x);
-		ser.syncArray(_sliderPuzzleState.playerTileOrder[i].data(), x, Common::Serializer::Sint16LE);
+			_sliderPuzzleState->playerTileOrder.resize(y);
+
+			for (int i = 0; i < y; ++i) {
+				_sliderPuzzleState->playerTileOrder[i].resize(x);
+				ser.syncArray(_sliderPuzzleState->playerTileOrder[i].data(), x, Common::Serializer::Sint16LE);
+			}
+
+			break;
+		}
+		default:
+			break;
 	}
 }
 
@@ -515,7 +532,19 @@ void Scene::init() {
 		_lastHintCharacter = _lastHintID = -1;
 	}
 
-	_sliderPuzzleState.playerHasTriedPuzzle = false;
+	// Initialize game-specific data
+	switch (g_nancy->getGameType()) {
+		case kGameTypeVampire:
+			// Fall through to avoid having to bump the savefile version
+			// fall through
+		case kGameTypeNancy1:
+			delete _sliderPuzzleState;
+			_sliderPuzzleState = new SliderPuzzleState();
+			_sliderPuzzleState->playerHasTriedPuzzle = false;
+			break;
+		default:
+			break;
+	}
 
 	initStaticData();
 
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index ea367da1529..eed22634ef7 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -238,11 +238,6 @@ private:
 		int16 primaryVideoResponsePicked = -1;
 	};
 
-	struct SliderPuzzleState {
-		Common::Array<Common::Array<int16>> playerTileOrder;
-		bool playerHasTriedPuzzle;
-	};
-
 	// UI
 	UI::FullScreenImage _frame;
 	UI::Viewport _viewport;
@@ -258,11 +253,13 @@ private:
 	UI::InventoryBoxOrnaments *_inventoryBoxOrnaments;
 	UI::Clock *_clock;
 
-	// Data
+	// Game-specific data that needs to be saved/loaded
+	SliderPuzzleState *_sliderPuzzleState;
+
+	// General data
 	SceneState _sceneState;
 	PlayFlags _flags;
 	Timers _timers;
-	SliderPuzzleState _sliderPuzzleState;
 	uint16 _difficulty;
 	Common::Array<uint16> _hintsRemaining;
 	int16 _lastHintCharacter;


Commit: 970d6c961e398ce08838e7ca9df811827954dcd5
    https://github.com/scummvm/scummvm/commit/970d6c961e398ce08838e7ca9df811827954dcd5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-16T15:40:51+03:00

Commit Message:
NANCY: Do not autosave when scene is not running

Fixed an issue where autosaves would trigger when loading
a game from the launcher, resulting in an invalid save.

Changed paths:
    engines/nancy/nancy.cpp


diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index f4da3796e91..368d7088f82 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -132,7 +132,9 @@ bool NancyEngine::canLoadGameStateCurrently() {
 
 bool NancyEngine::canSaveGameStateCurrently() {
 	// TODO also disable during secondary movie
-	return State::Scene::hasInstance() && NancySceneState.getActiveConversation() == nullptr;
+	return State::Scene::hasInstance() &&
+			NancySceneState._state == State::Scene::kRun &&
+			NancySceneState.getActiveConversation() == nullptr;
 }
 
 bool NancyEngine::canSaveAutosaveCurrently() {


Commit: 471bcccfe423d85cc98c86f666b2383536cdc0f3
    https://github.com/scummvm/scummvm/commit/471bcccfe423d85cc98c86f666b2383536cdc0f3
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-04-16T15:40:51+03:00

Commit Message:
NANCY: Implement RippedLetterPuzzle

Implemented the RippedLetterPuzzle action record,
which has the player rearrange torn pieces of a letter.
Added the kRotate cursor type. Added a custom function
for rotating a surface by a right angle.

Changed paths:
  A engines/nancy/action/rippedletterpuzzle.cpp
  A engines/nancy/action/rippedletterpuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/commontypes.h
    engines/nancy/cursor.h
    engines/nancy/graphics.cpp
    engines/nancy/graphics.h
    engines/nancy/module.mk
    engines/nancy/state/scene.cpp
    engines/nancy/state/scene.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 66eb63c7500..74f8f6a8398 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -30,6 +30,7 @@
 #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/state/scene.h"
 
@@ -143,6 +144,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new PlaySoundMultiHS();
 	case 160:
 		return new HintSystem();
+	case 203:
+		return new RippedLetterPuzzle();
 	default:
 		error("Action Record type %i is invalid!", type);
 		return nullptr;
diff --git a/engines/nancy/action/rippedletterpuzzle.cpp b/engines/nancy/action/rippedletterpuzzle.cpp
new file mode 100644
index 00000000000..bc0f273a84f
--- /dev/null
+++ b/engines/nancy/action/rippedletterpuzzle.cpp
@@ -0,0 +1,329 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/util.h"
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/cursor.h"
+
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/action/rippedletterpuzzle.h"
+
+#include "graphics/transform_struct.h"
+
+namespace Nancy {
+namespace Action {
+
+void RippedLetterPuzzle::init() {
+	Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
+	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
+	_drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+	setTransparent(true);
+	setVisible(true);
+	moveTo(screenBounds);
+
+	_pickedUpPiece._drawSurface.create(_destRects[0].width(), _destRects[0].height(), g_nancy->_graphicsManager->getInputPixelFormat());
+	_pickedUpPiece.setVisible(false);
+
+	g_nancy->_resource->loadImage(_imageName, _image);
+}
+
+void RippedLetterPuzzle::registerGraphics() {
+	_pickedUpPiece.registerGraphics();
+	RenderObject::registerGraphics();
+}
+
+void RippedLetterPuzzle::readData(Common::SeekableReadStream &stream) {
+	_puzzleState = NancySceneState._rippedLetterPuzzleState;
+	assert(_puzzleState);
+
+	readFilename(stream, _imageName);
+
+	readRectArray(stream, _srcRects, 24);
+	readRectArray(stream, _destRects, 24);
+
+	readRect(stream, _rotateHotspot);
+	readRect(stream, _takeHotspot);
+	readRect(stream, _dropHotspot);
+
+	_initOrder.resize(24);
+	for (uint i = 0; i < 24; ++i) {
+		_initOrder[i] = stream.readByte();
+	}
+
+	_initRotations.resize(24);
+	for (uint i = 0; i < 24; ++i) {
+		_initRotations[i] = stream.readByte();
+	}
+
+	_solveOrder.resize(24);
+	for (uint i = 0; i < 24; ++i) {
+		_solveOrder[i] = stream.readByte();
+	}
+
+	_solveRotations.resize(24);
+	for (uint i = 0; i < 24; ++i) {
+		_solveRotations[i] = stream.readByte();
+	}
+
+	_takeSound.read(stream, SoundDescription::kNormal);
+	_dropSound.read(stream, SoundDescription::kNormal);
+	_rotateSound.read(stream, SoundDescription::kNormal);
+
+	_solveExitScene.readData(stream);
+	stream.skip(2); // shouldStopRendering, useless
+
+	_flagOnSolve.label = stream.readSint16LE();
+	_flagOnSolve.flag = stream.readByte();
+
+	_solveSound.read(stream, SoundDescription::kNormal);
+
+	_exitScene.readData(stream);
+
+	stream.skip(2); // shouldStopRendering, useless
+	_flagOnExit.label = stream.readSint16LE();
+	_flagOnExit.flag = stream.readByte();
+
+	readRect(stream, _exitHotspot);
+}
+
+void RippedLetterPuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		registerGraphics();
+		if (!_puzzleState->playerHasTriedPuzzle) {
+			_puzzleState->order = _initOrder;
+			_puzzleState->rotations = _initRotations;
+			_puzzleState->playerHasTriedPuzzle = true;
+		}
+
+		for (uint i = 0; i < 24; ++i) {
+			drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
+		}
+
+		g_nancy->_sound->loadSound(_takeSound);
+		g_nancy->_sound->loadSound(_dropSound);
+		g_nancy->_sound->loadSound(_rotateSound);
+
+		_state = kRun;
+		// fall through
+	case kRun:
+		switch (_solveState) {
+		case kNotSolved :
+			for (uint i = 0; i < 24; ++i) {
+				if (_puzzleState->order[i] != _solveOrder[i] || _puzzleState->rotations[i] != _solveRotations[i]) {
+					return;
+				}
+			}
+
+			g_nancy->_sound->loadSound(_solveSound);
+			g_nancy->_sound->playSound(_solveSound);
+			_solveState = kWaitForSound;
+			break;
+		case kWaitForSound :
+			if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
+				g_nancy->_sound->stopSound(_solveSound);
+				_state = kActionTrigger;
+			}
+
+			break;
+		}
+
+		break;
+	case kActionTrigger :
+		switch (_solveState) {
+		case kNotSolved:
+			NancySceneState.changeScene(_exitScene);
+			NancySceneState.setEventFlag(_flagOnExit);
+			break;
+		case kWaitForSound:
+			NancySceneState.changeScene(_solveExitScene);
+			NancySceneState.setEventFlag(_flagOnSolve);
+			_puzzleState->playerHasTriedPuzzle = false;
+			break;
+		}
+
+		g_nancy->_sound->stopSound(_takeSound);
+		g_nancy->_sound->stopSound(_dropSound);
+		g_nancy->_sound->stopSound(_rotateSound);
+		finishExecution();
+	}
+}
+
+void RippedLetterPuzzle::handleInput(NancyInput &input) {
+	for (uint i = 0; i < 24; ++i) {
+		Common::Rect screenHotspot = NancySceneState.getViewport().convertViewportToScreen(_destRects[i]);
+		if (screenHotspot.contains(input.mousePos)) {
+			Common::Rect insideRect;
+			if (_pickedUpPieceID == -1) {
+				// No piece picked up
+
+				// Check if the mouse is inside the rotation hotspot
+				insideRect = _rotateHotspot;
+				insideRect.translate(screenHotspot.left, screenHotspot.top);
+
+				if (insideRect.contains(input.mousePos)) {
+					g_nancy->_cursorManager->setCursorType(CursorManager::kRotate);
+
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						// Player has clicked, rotate the piece
+						if (++_puzzleState->rotations[i] > 3) {
+							_puzzleState->rotations[i] = 0;
+						}
+
+						drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
+						g_nancy->_sound->playSound(_rotateSound);
+					}
+
+					break;
+				}
+
+				// Check if the mouse is inside the pickup hotspot
+				insideRect = _takeHotspot;
+				insideRect.translate(screenHotspot.left, screenHotspot.top);
+
+				if (insideRect.contains(input.mousePos)) {
+					g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						// Player has clicked, take the piece
+
+						// First, copy the graphic from the full drawSurface...
+						_pickedUpPiece._drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+						_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
+						_pickedUpPiece.setVisible(true);
+						_pickedUpPiece.setTransparent(true);
+
+						// ...then change the data...
+						_pickedUpPieceID = _puzzleState->order[i];
+						_pickedUpPieceRot = _puzzleState->rotations[i];
+						_puzzleState->order[i] = -1;
+
+						// ...then clear the piece from the drawSurface
+						drawPiece(i, 0);
+						
+						g_nancy->_sound->playSound(_takeSound);
+					}
+
+					break;
+				}
+			} else {
+				// Currently carrying a piece
+
+				// Check if the mouse is inside the drop hotspot
+				insideRect = _dropHotspot;
+				insideRect.translate(screenHotspot.left, screenHotspot.top);
+
+				if (insideRect.contains(input.mousePos)) {
+					g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+					if (input.input & NancyInput::kLeftMouseButtonUp) {
+						// Player has clicked, drop the piece and pick up a new one
+
+						// Check if we should pick up a new piece
+						if (_puzzleState->order[i] == -1) {
+							// No, hide the picked up piece graphic
+							_pickedUpPiece.setVisible(false);
+						} else {
+							// Yes, change the picked piece graphic
+							_pickedUpPiece._drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
+							_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
+							_pickedUpPiece.setVisible(true);
+							_pickedUpPiece.setTransparent(true);
+						}
+
+						SWAP<int8>(_puzzleState->order[i], _pickedUpPieceID);
+						SWAP<byte>(_puzzleState->rotations[i], _pickedUpPieceRot);
+
+						// Draw the newly placed piece
+						drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
+
+						g_nancy->_sound->playSound(_dropSound);
+					}
+
+					break;
+				}
+			}
+		}
+	}
+
+	// Now move the carried piece
+	if (_pickedUpPieceID != -1) {
+		// First, move the piece so its center is below the mouse hotspot
+		Common::Rect newLocation = _pickedUpPiece._drawSurface.getBounds();
+		newLocation.moveTo(input.mousePos.x, input.mousePos.y);
+		newLocation.translate(-newLocation.width() / 2, -newLocation.height() / 2);
+
+		// Then, make sure it doesn't escape outside the viewport bounds
+		Common::Rect screen = NancySceneState.getViewport().getScreenPosition();
+		
+		if (newLocation.left < screen.left) {
+			newLocation.translate(screen.left - newLocation.left, 0);
+		}
+
+		if (newLocation.top < screen.top) {
+			newLocation.translate(0, screen.top - newLocation.top);
+		}
+
+		if (newLocation.right > screen.right) {
+			newLocation.translate(screen.right - newLocation.right, 0);
+		}
+
+		if (newLocation.bottom > screen.bottom) {
+			newLocation.translate(0, screen.bottom - newLocation.bottom);
+		}
+
+		_pickedUpPiece.moveTo(newLocation);
+	} else {
+		// No piece picked up, check the exit hotspot
+		if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+			g_nancy->_cursorManager->setCursorType(CursorManager::kExit);
+
+			if (input.input & NancyInput::kLeftMouseButtonUp) {
+				// Player has clicked, exit
+				_state = kActionTrigger;
+			}
+		}
+	}
+}
+
+void RippedLetterPuzzle::drawPiece(const uint pos, const byte rotation, const int pieceID) {
+	// Clear the selected position
+	_drawSurface.fillRect(_destRects[pos], _drawSurface.getTransparentColor());
+	_needsRedraw = true;
+
+	// No piece, just clear
+	if (pieceID == -1) {
+		return;
+	}
+
+	// Create temporary ManagedSurfaces and call the custom rotation function
+	Graphics::ManagedSurface srcSurf(_image, _srcRects[pieceID]);
+	Graphics::ManagedSurface destSurf(_drawSurface, _destRects[pos]);
+	GraphicsManager::rotateBlit(srcSurf, destSurf, rotation);
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/rippedletterpuzzle.h b/engines/nancy/action/rippedletterpuzzle.h
new file mode 100644
index 00000000000..6d4f57a25cf
--- /dev/null
+++ b/engines/nancy/action/rippedletterpuzzle.h
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_RIPPEDLETTERPUZZLE_H
+#define NANCY_ACTION_RIPPEDLETTERPUZZLE_H
+
+#include "engines/nancy/renderobject.h"
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+class RippedLetterPuzzle : public ActionRecord, public RenderObject {
+public:
+	enum SolveState { kNotSolved, kWaitForSound };
+	RippedLetterPuzzle() : RenderObject(7), _pickedUpPiece(8) {}
+	virtual ~RippedLetterPuzzle() {}
+
+	void init() override;
+	void registerGraphics() override;
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	void handleInput(NancyInput &input) override;
+
+	Common::String _imageName;
+	Common::Array<Common::Rect> _srcRects;
+	Common::Array<Common::Rect> _destRects;
+	Common::Rect _rotateHotspot;
+	Common::Rect _takeHotspot;
+	Common::Rect _dropHotspot;
+	Common::Array<int8> _initOrder;
+	Common::Array<byte> _initRotations;
+	Common::Array<int8> _solveOrder;
+	Common::Array<byte> _solveRotations;
+	SoundDescription _takeSound;
+	SoundDescription _dropSound;
+	SoundDescription _rotateSound;
+	SceneChangeDescription _solveExitScene;
+	SoundDescription _solveSound;
+	FlagDescription _flagOnSolve;
+	SceneChangeDescription _exitScene;
+	FlagDescription _flagOnExit;
+	Common::Rect _exitHotspot;
+
+	RenderObject _pickedUpPiece;
+	int8 _pickedUpPieceID = -1;
+	byte _pickedUpPieceRot = 0;
+	
+	Graphics::ManagedSurface _image;
+	SolveState _solveState = kNotSolved;
+	RippedLetterPuzzleState *_puzzleState = nullptr;
+
+protected:
+	Common::String getRecordTypeName() const override { return "RippedLetterPuzzle"; }
+	bool isViewportRelative() const override { return true; }
+
+	void drawPiece(const uint pos, const byte rotation, const int pieceID = -1);
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif //NANCY_ACTION_RIPPEDLETTERPUZZLE_H
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 5c62c4f8d18..b8b46865fbe 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -246,6 +246,12 @@ struct SliderPuzzleState {
 	bool playerHasTriedPuzzle;
 };
 
+struct RippedLetterPuzzleState {
+	Common::Array<int8> order;
+	Common::Array<byte> rotations;
+	bool playerHasTriedPuzzle;
+};
+
 } // End of namespace Nancy
 
 #endif // NANCY_COMMONYPES_H
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index c7122cf1c61..88a7f026e45 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -34,7 +34,7 @@ class CursorManager {
 	friend class NancyEngine;
 
 public:
-	enum CursorType { kNormal = 0, kHotspot = 1, kMove = 2, kExit = 3, kNormalArrow, kHotspotArrow };
+	enum CursorType { kNormal = 0, kHotspot = 1, kMove = 2, kExit = 3, kRotate = 4, kNormalArrow, kHotspotArrow };
 
 	CursorManager() :
 		_isInitialized(false),
diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index 639501eedcf..3753ae1e9d2 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -263,6 +263,62 @@ void GraphicsManager::copyToManaged(void *src, Graphics::ManagedSurface &dst, ui
 	copyToManaged(surf, dst, verticalFlip, doubleSize);
 }
 
+// Custom rotation code since Surface::rotoscale() produces incorrect results
+// Only works on 16 bit square surfaces with the same size, and ignores transparency
+// Rotation is a value between 0 and 3, corresponding to 0, 90, 180, or 270 degrees clockwise
+void GraphicsManager::rotateBlit(const Graphics::ManagedSurface &src, Graphics::ManagedSurface &dest, byte rotation) {
+	assert(!src.empty() && !dest.empty());
+	assert(src.w == src.h && src.h == dest.w && dest.w == dest.h);
+	assert(rotation >= 0 && rotation <= 3);
+	assert(src.format.bytesPerPixel == 2 && dest.format.bytesPerPixel == 2);
+
+	uint size = src.w;
+	const uint16 *s, *e;
+
+	switch (rotation) {
+	case 0 :
+		// No rotation, just blit
+		dest.rawBlitFrom(src, src.getBounds(), Common::Point());
+		return;
+	case 2 : {
+		// 180 degrees
+		uint16 *d;
+		for (uint y = 0; y < size; ++y) {
+			s = (const uint16 *)src.getBasePtr(0, y);
+			e = (const uint16 *)src.getBasePtr(size - 1, y);
+			d = (uint16 *)dest.getBasePtr(size - 1, size - y - 1);
+			for (; s < e; ++s, --d) {
+				*d = *s;
+			}
+		}
+
+		break;
+	}
+	case 1 :
+		// 90 degrees
+		for (uint y = 0; y < size; ++y) {
+			s = (const uint16 *)src.getBasePtr(0, y);
+			e = (const uint16 *)src.getBasePtr(size - 1, y);
+			for (uint x = 0; x < size; ++x, ++s) {
+				*((uint16 *)dest.getBasePtr(size - y - 1, x)) = *s;
+			}
+		}
+
+		break;
+	case 3 :
+		// 270 degrees
+		for (uint y = 0; y < size; ++y) {
+			s = (const uint16 *)src.getBasePtr(0, y);
+			e = (const uint16 *)src.getBasePtr(size - 1, y);
+			for (uint x = 0; x < size; ++x, ++s) {
+				*((uint16 *)dest.getBasePtr(y, size - x - 1)) = *s;
+			}
+		}
+
+		break;
+	}
+}
+
 void GraphicsManager::debugDrawToScreen(const Graphics::ManagedSurface &surf) {
 	_screen.blitFrom(surf, Common::Point());
 	_screen.update();
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index c6d11730f00..713174bbed8 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -61,6 +61,8 @@ public:
 	static void copyToManaged(const Graphics::Surface &src, Graphics::ManagedSurface &dst, bool verticalFlip = false, bool doubleSize = false);
 	static void copyToManaged(void *src, Graphics::ManagedSurface &dst, uint srcW, uint srcH, const Graphics::PixelFormat &format, bool verticalFlip = false, bool doubleSize = false);
 
+	static void rotateBlit(const Graphics::ManagedSurface &src, Graphics::ManagedSurface &dest, byte rotation);
+
 	// Debug
 	void debugDrawToScreen(const Graphics::ManagedSurface &surf);
 
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index d20c568e6f0..fb96c90b751 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS = \
   action/passwordpuzzle.o \
   action/conversation.o \
   action/recordtypes.o \
+  action/rippedletterpuzzle.o \
   action/rotatinglockpuzzle.o \
   action/secondarymovie.o \
   action/secondaryvideo.o \
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 51dd8baadb6..4c2e9fc2069 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -114,7 +114,8 @@ Scene::Scene() :
 		_difficulty(0),
 		_activeConversation(nullptr),
 		_lightning(nullptr),
-		_sliderPuzzleState(nullptr) {}
+		_sliderPuzzleState(nullptr),
+		_rippedLetterPuzzleState(nullptr) {}
 
 Scene::~Scene()  {
 	delete _helpButton;
@@ -125,6 +126,7 @@ Scene::~Scene()  {
 	delete _clock;
 	delete _lightning;
 	delete _sliderPuzzleState;
+	delete _rippedLetterPuzzleState;
 }
 
 void Scene::process() {
@@ -465,42 +467,58 @@ void Scene::synchronize(Common::Serializer &ser) {
 	ser.syncAsSint16LE(_lastHintID);
 
 	switch (g_nancy->getGameType()) {
-		case kGameTypeVampire:
-			// Fall through to avoid having to bump the savegame version
-			// fall through
-		case kGameTypeNancy1: {
-			// Synchronize SliderPuzzle static data
-			if (!_sliderPuzzleState) {
-				return;
-			}
+	case kGameTypeVampire:
+		// Fall through to avoid having to bump the savegame version
+		// fall through
+	case kGameTypeNancy1: {
+		// Synchronize SliderPuzzle static data
+		if (!_sliderPuzzleState) {
+			return;
+		}
 
-			ser.syncAsByte(_sliderPuzzleState->playerHasTriedPuzzle);
+		ser.syncAsByte(_sliderPuzzleState->playerHasTriedPuzzle);
 
-			byte x = 0, y = 0;
+		byte x = 0, y = 0;
 
-			if (ser.isSaving()) {
-				y = _sliderPuzzleState->playerTileOrder.size();
-				if (y) {
-					x = _sliderPuzzleState->playerTileOrder.back().size();
-				} else {
-					x = 0;
-				}
+		if (ser.isSaving()) {
+			y = _sliderPuzzleState->playerTileOrder.size();
+			if (y) {
+				x = _sliderPuzzleState->playerTileOrder.back().size();
+			} else {
+				x = 0;
 			}
+		}
 
-			ser.syncAsByte(x);
-			ser.syncAsByte(y);
+		ser.syncAsByte(x);
+		ser.syncAsByte(y);
 
-			_sliderPuzzleState->playerTileOrder.resize(y);
+		_sliderPuzzleState->playerTileOrder.resize(y);
 
-			for (int i = 0; i < y; ++i) {
-				_sliderPuzzleState->playerTileOrder[i].resize(x);
-				ser.syncArray(_sliderPuzzleState->playerTileOrder[i].data(), x, Common::Serializer::Sint16LE);
-			}
+		for (int i = 0; i < y; ++i) {
+			_sliderPuzzleState->playerTileOrder[i].resize(x);
+			ser.syncArray(_sliderPuzzleState->playerTileOrder[i].data(), x, Common::Serializer::Sint16LE);
+		}
 
+		break;
+	}
+	case kGameTypeNancy2 :
+		if (!_rippedLetterPuzzleState) {
 			break;
 		}
-		default:
-			break;
+
+		ser.syncAsByte(_rippedLetterPuzzleState->playerHasTriedPuzzle);
+
+		if (ser.isLoading()) {
+			_rippedLetterPuzzleState->order.resize(24);
+			_rippedLetterPuzzleState->rotations.resize(24);
+		}
+
+		ser.syncArray(_rippedLetterPuzzleState->order.data(), 24, Common::Serializer::Byte);
+		ser.syncArray(_rippedLetterPuzzleState->rotations.data(), 24, Common::Serializer::Byte);
+
+		break;
+	default:
+		break;
 	}
 }
 
@@ -534,16 +552,22 @@ void Scene::init() {
 
 	// Initialize game-specific data
 	switch (g_nancy->getGameType()) {
-		case kGameTypeVampire:
-			// Fall through to avoid having to bump the savefile version
-			// fall through
-		case kGameTypeNancy1:
-			delete _sliderPuzzleState;
-			_sliderPuzzleState = new SliderPuzzleState();
-			_sliderPuzzleState->playerHasTriedPuzzle = false;
-			break;
-		default:
-			break;
+	case kGameTypeVampire:
+		// Fall through to avoid having to bump the savefile version
+		// fall through
+	case kGameTypeNancy1:
+		delete _sliderPuzzleState;
+		_sliderPuzzleState = new SliderPuzzleState();
+		_sliderPuzzleState->playerHasTriedPuzzle = false;
+		break;
+	case kGameTypeNancy2:
+		delete _rippedLetterPuzzleState;
+		_rippedLetterPuzzleState = new RippedLetterPuzzleState();
+		_rippedLetterPuzzleState->playerHasTriedPuzzle = false;
+		_rippedLetterPuzzleState->order.resize(24, 0);
+		_rippedLetterPuzzleState->rotations.resize(24, 0);
+	default:
+		break;
 	}
 
 	initStaticData();
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index eed22634ef7..0394a63f7f9 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -76,7 +76,6 @@ struct SceneInfo {
 class Scene : public State, public Common::Singleton<Scene> {
 	friend class Nancy::Action::ActionRecord;
 	friend class Nancy::Action::ActionManager;
-	friend class Nancy::Action::SliderPuzzle;
 	friend class Nancy::NancyConsole;
 	friend class Nancy::NancyEngine;
 
@@ -187,6 +186,10 @@ public:
 	// The Vampire Diaries only;
 	void beginLightning(int16 distance, uint16 pulseTime, int16 rgbPercent);
 
+	// Game-specific data that needs to be saved/loaded
+	SliderPuzzleState *_sliderPuzzleState;
+	RippedLetterPuzzleState *_rippedLetterPuzzleState;
+
 private:
 	void init();
 	void load();
@@ -253,9 +256,6 @@ private:
 	UI::InventoryBoxOrnaments *_inventoryBoxOrnaments;
 	UI::Clock *_clock;
 
-	// Game-specific data that needs to be saved/loaded
-	SliderPuzzleState *_sliderPuzzleState;
-
 	// General data
 	SceneState _sceneState;
 	PlayFlags _flags;




More information about the Scummvm-git-logs mailing list