[Scummvm-git-logs] scummvm master -> 79519ca67096ad671ea21a5c5ee807d302c75bb3

bluegr noreply at scummvm.org
Wed Jul 1 01:15:56 UTC 2026


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

Summary:
6a2a3f57c1 NANCY: NANCY12: Implement differences for SortPuzzle
6e1b43baf8 NANCY: NANCY12: Implement differences for OrderingPuzzle
f1adf9bdad NANCY: Restore the mouse cursor when a secondary movie finishes
79519ca670 NANCY: NANCY12: Implement the Set3DSoundListenerPosition AR


Commit: 6a2a3f57c1cbfafe6abbf695318a9dda17321c04
    https://github.com/scummvm/scummvm/commit/6a2a3f57c1cbfafe6abbf695318a9dda17321c04
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-01T04:15:43+03:00

Commit Message:
NANCY: NANCY12: Implement differences for SortPuzzle

Changed paths:
    engines/nancy/action/puzzle/sortpuzzle.cpp
    engines/nancy/action/puzzle/sortpuzzle.h


diff --git a/engines/nancy/action/puzzle/sortpuzzle.cpp b/engines/nancy/action/puzzle/sortpuzzle.cpp
index 38e2772408d..b5304b93daf 100644
--- a/engines/nancy/action/puzzle/sortpuzzle.cpp
+++ b/engines/nancy/action/puzzle/sortpuzzle.cpp
@@ -73,6 +73,11 @@ static bool unpackGrid(const Common::Array<int16> &in, SortPuzzle::Cell grid[][S
 }
 
 void SortPuzzle::readData(Common::SeekableReadStream &stream) {
+	if (g_nancy->getGameType() >= kGameTypeNancy12) {
+		readDataNancy12(stream);
+		return;
+	}
+
 	readFilename(stream, _boardImageName);
 	readFilename(stream, _cursorImageName);
 
@@ -121,6 +126,81 @@ void SortPuzzle::readData(Common::SeekableReadStream &stream) {
 	_cellHeight = _cellSrcRects[0][0].height();
 }
 
+// Nancy 12 reworked the record: the board layout is computed from a single cell
+// size plus origin/spacing (rather than a per-position rect grid), gems are drawn
+// from one source sprite per value, and there is an (unused-here) preset board and
+// shuffle flag. The engine reshuffles itself, so those are skipped.
+void SortPuzzle::readDataNancy12(Common::SeekableReadStream &stream) {
+	readFilename(stream, _boardImageName);  // 0x000
+	readFilename(stream, _cursorImageName); // 0x021
+	_retainState = (stream.readByte() != 0); // 0x042
+	_rows = stream.readUint16LE();           // 0x043
+	_cols = stream.readUint16LE();           // 0x045
+	stream.skip(4);                          // 0x047 unknown
+	stream.skip(1);                          // 0x04b preset/shuffle flag (engine reshuffles)
+	stream.skip(200);                        // 0x04c preset board values (100 x int16)
+	stream.skip(2);                          // 0x114 unknown
+	_groupDivisor = stream.readUint16LE();   // 0x116
+	_valueRange   = stream.readUint16LE();   // 0x118
+
+	_valueSrcRects.resize(kNumValueRects);   // 0x11a: one source sprite per gem value
+	for (uint i = 0; i < (uint)kNumValueRects; ++i) {
+		readRect(stream, _valueSrcRects[i]);
+	}
+
+	_originX  = stream.readUint16LE();        // 0x83a
+	_originY  = stream.readUint16LE();        // 0x83c
+	_spacingY = stream.readUint16LE();        // 0x83e
+	_spacingX = stream.readUint16LE();        // 0x840
+
+	_pickupSound.readNormal(stream);          // 0x842
+	_dropSound.readNormal(stream);            // 0x873
+
+	_winScene.readData(stream);               // 0x8a4
+	stream.skip(2);
+	_winFlag.label = stream.readSint16LE();
+	_winFlag.flag  = stream.readByte();
+	_winSound.readNormal(stream);             // 0x8bd
+
+	_cancelScene.readData(stream);            // 0x8ee
+	stream.skip(2);
+	_cancelFlag.label = stream.readSint16LE();
+	_cancelFlag.flag  = stream.readByte();
+
+	readRect(stream, _exitHotspot);           // 0x907
+	stream.skip(2); // exit cursor type id
+
+	if (_rows > kMaxRows) _rows = kMaxRows;
+	if (_cols > kMaxCols) _cols = kMaxCols;
+	if (_groupDivisor == 0) _groupDivisor = 1;
+	if (_valueRange == 0) _valueRange = 1;
+
+	_cellWidth  = _valueSrcRects[0].width();
+	_cellHeight = _valueSrcRects[0].height();
+}
+
+Common::Rect SortPuzzle::cellSprite(const Cell &cell) const {
+	if (g_nancy->getGameType() >= kGameTypeNancy12) {
+		// Each pie has a kind (its group/column, cell.srcCol) and a size
+		// (cell.value, smallest to biggest). The source sprites are laid out as a
+		// grid - kind * kSizesPerKind + size - so the index must combine both,
+		// otherwise every kind would draw the same pie at a given size.
+		if (cell.srcCol >= 0 && cell.value >= 0) {
+			uint idx = (uint)cell.srcCol * kSizesPerKind + (uint)cell.value;
+			if (idx < _valueSrcRects.size()) {
+				return _valueSrcRects[idx];
+			}
+		}
+		return Common::Rect();
+	}
+
+	if (cell.srcRow >= 0 && cell.srcRow < kMaxSourceRows &&
+			cell.srcCol >= 0 && cell.srcCol < kMaxSourceCols) {
+		return _cellSrcRects[cell.srcRow][cell.srcCol];
+	}
+	return Common::Rect();
+}
+
 void SortPuzzle::initState() {
 	SortPuzzleData *spd = (SortPuzzleData *)NancySceneState.getPuzzleData(SortPuzzleData::getTag());
 	if (_retainState && spd && !spd->currentState.empty() && !spd->solvedState.empty()) {
@@ -373,10 +453,7 @@ void SortPuzzle::redraw() {
 			const Cell &cell = _current[r][c];
 			if (cell.isEmpty)
 				continue;
-			if (cell.srcRow < 0 || cell.srcRow >= kMaxSourceRows ||
-			    cell.srcCol < 0 || cell.srcCol >= kMaxSourceCols)
-				continue;
-			const Common::Rect &src = _cellSrcRects[cell.srcRow][cell.srcCol];
+			Common::Rect src = cellSprite(cell);
 			if (src.isEmpty())
 				continue;
 			Common::Rect dst = cellRect(r, c);
@@ -386,7 +463,9 @@ void SortPuzzle::redraw() {
 
 	if (_hasHeld) {
 		bool drawn = false;
-		if (_held.value >= 0 && _held.value < kNumCursors) {
+		// Older games carry a separate cursor image with one sprite per value;
+		// Nancy 12 has no cursor image and draws the held gem from the board image.
+		if (g_nancy->getGameType() < kGameTypeNancy12 && _held.value >= 0 && _held.value < kNumCursors) {
 			const Common::Rect &src = _cursorSrcRects[_held.value];
 			if (!src.isEmpty()) {
 				int x = _heldDrawPos.x - src.width()  / 2;
@@ -395,9 +474,8 @@ void SortPuzzle::redraw() {
 				drawn = true;
 			}
 		}
-		if (!drawn && _held.srcRow >= 0 && _held.srcRow < kMaxSourceRows &&
-		    _held.srcCol >= 0 && _held.srcCol < kMaxSourceCols) {
-			const Common::Rect &src = _cellSrcRects[_held.srcRow][_held.srcCol];
+		if (!drawn) {
+			Common::Rect src = cellSprite(_held);
 			if (!src.isEmpty()) {
 				int x = _heldDrawPos.x - _cellWidth  / 2;
 				int y = _heldDrawPos.y - _cellHeight / 2;
diff --git a/engines/nancy/action/puzzle/sortpuzzle.h b/engines/nancy/action/puzzle/sortpuzzle.h
index 5002db89dd6..42ff9ebe781 100644
--- a/engines/nancy/action/puzzle/sortpuzzle.h
+++ b/engines/nancy/action/puzzle/sortpuzzle.h
@@ -41,6 +41,7 @@ public:
 	void init() override;
 
 	void readData(Common::SeekableReadStream &stream) override;
+	void readDataNancy12(Common::SeekableReadStream &stream);
 	void execute() override;
 	void handleInput(NancyInput &input) override;
 
@@ -62,6 +63,8 @@ protected:
 	static const int kMaxSourceRows = 8;
 	static const int kMaxSourceCols = 5;
 	static const int kNumCursors    = 10;
+	static const int kNumValueRects = 114; // Nancy 12: per-kind/size source sprites (19 kinds x 6 sizes)
+	static const int kSizesPerKind  = 6;   // Nancy 12: stride between kinds in _valueSrcRects
 
 	// File data
 
@@ -76,6 +79,7 @@ protected:
 
 	Common::Rect _cellSrcRects[kMaxSourceRows][kMaxSourceCols];
 	Common::Rect _cursorSrcRects[kNumCursors];
+	Common::Array<Common::Rect> _valueSrcRects; // Nancy 12: indexed by gem value
 
 	uint16 _originX  = 0;
 	uint16 _originY  = 0;
@@ -124,6 +128,7 @@ protected:
 	void redraw();
 	void checkSolved();
 	Common::Rect cellRect(int row, int col) const;
+	Common::Rect cellSprite(const Cell &cell) const;
 	bool hitTestCell(const Common::Point &p, int &outRow, int &outCol) const;
 };
 


Commit: 6e1b43baf83866bb2c7c308b7e608f22df914c63
    https://github.com/scummvm/scummvm/commit/6e1b43baf83866bb2c7c308b7e608f22df914c63
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-01T04:15:45+03:00

Commit Message:
NANCY: NANCY12: Implement differences for OrderingPuzzle

Changed paths:
    engines/nancy/action/puzzle/orderingpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/orderingpuzzle.cpp b/engines/nancy/action/puzzle/orderingpuzzle.cpp
index eedd6ab2ab1..766530b558e 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -224,28 +224,39 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 
 	if (isKeypad && g_nancy->getGameType() >= kGameTypeNancy7) {
 		if (_puzzleType == kKeypad) {
-			if (g_nancy->getGameType() >= kGameTypeNancy11) {
-				// Nancy 11 multi-stage keypad: a stage count, per-stage display rects, the codes
-				// for stages 1+, a final code matrix and an alternate scene, then the button rects.
-				_numStages = stream.readUint16LE();
-				stream.skip(20 * 16 + 1); // per-stage display rects + blink flag
-
-				_stageSequences.resize(4);
-				_stageCheckOrder.resize(4);
-				for (uint s = 0; s < 4; ++s) {
-					uint16 len = stream.readUint16LE();
-					_stageCheckOrder[s] = (stream.readByte() != 0);
-					len = MIN<uint16>(len, 30);
-					_stageSequences[s].resize(len);
-					for (uint16 k = 0; k < len; ++k)
-						_stageSequences[s][k] = stream.readByte();
-					stream.skip(30 - len);
-				}
+			if (g_nancy->getGameType() >= kGameTypeNancy12) {
+				// Nancy 12 reworked the keypad block: grid dimensions and the per-stage
+				// codes (the win still uses the sequence read above), then three button
+				// rect arrays - sprite source rects, on-screen dest positions, and tighter
+				// per-button hotspots. The dest rects double as the hotspots here.
+				stream.skip(0x309 - 0x10c); // grid + per-stage codes (not modeled)
+				readRectArray(stream, _down1Rects, numElements, 30); // button sprite source rects
+				readRectArray(stream, _destRects, numElements, 30);  // on-screen button positions
+				stream.skip(30 * 16);       // tighter per-button hotspots (we use _destRects)
+			} else {
+				if (g_nancy->getGameType() >= kGameTypeNancy11) {
+					// Nancy 11 multi-stage keypad: a stage count, per-stage display rects, the codes
+					// for stages 1+, a final code matrix and an alternate scene, then the button rects.
+					_numStages = stream.readUint16LE();
+					stream.skip(20 * 16 + 1); // per-stage display rects + blink flag
+
+					_stageSequences.resize(4);
+					_stageCheckOrder.resize(4);
+					for (uint s = 0; s < 4; ++s) {
+						uint16 len = stream.readUint16LE();
+						_stageCheckOrder[s] = (stream.readByte() != 0);
+						len = MIN<uint16>(len, 30);
+						_stageSequences[s].resize(len);
+						for (uint16 k = 0; k < len; ++k)
+							_stageSequences[s][k] = stream.readByte();
+						stream.skip(30 - len);
+					}
 
-				stream.skip(25 + 25); // final code matrix + alternate scene (unused by the sequential model)
+					stream.skip(25 + 25); // final code matrix + alternate scene (unused by the sequential model)
+				}
+				readRectArray(ser, _down1Rects, numElements, maxNumElements);
+				readRectArray(ser, _destRects, numElements, maxNumElements);
 			}
-			readRectArray(ser, _down1Rects, numElements, maxNumElements);
-			readRectArray(ser, _destRects, numElements, maxNumElements);
 		} else if (_puzzleType == kKeypadTerse) {
 			// Terse elements are the same size & placed on a grid (in the source image AND on screen)
 			uint16 columns = stream.readUint16LE();
@@ -308,6 +319,11 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 			}
 		}
 
+		if (g_nancy->getGameType() >= kGameTypeNancy12 && _puzzleType == kKeypadTerse) {
+			// Nancy 12 keypad-terse grew by 4 bytes (exact layout not yet mapped).
+			stream.skip(4);
+		}
+
 		_hotspots = _destRects;
 	}
 


Commit: f1adf9bdad8cba408d16a031249df455f82390b2
    https://github.com/scummvm/scummvm/commit/f1adf9bdad8cba408d16a031249df455f82390b2
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-01T04:15:46+03:00

Commit Message:
NANCY: Restore the mouse cursor when a secondary movie finishes

This follows what the original does, and allows the player to
interact with the "Go fish" automaton game near the library in
Nancy 11.

Changed the original solution for the culprit confrontation bug
#16728 into a workaround, so it only applies to the culprit
confrontation scenes in Nancy 8.

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


diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 15b9e5dfe0d..1b1e5d2152d 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -509,6 +509,16 @@ void PlaySecondaryMovie::execute() {
 			_isFinished = false;
 			_decoder->seek(0);
 			_decoder->pauseVideo(false);
+		} else if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
+			// The movie finished and isn't looping, so restore the cursor now.
+			// WORKAROUND: Don't restore the cursor for Nancy 8, scenes 5420 - 5422
+			// (confrontation with the culprit). For some reason, the original engine
+			// doesn't restore the cursor in this scene - restoring it allows the user
+			// to examine items and break the scene itself.
+			// Refer to bug #16728 for more details
+			const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
+			if (!(g_nancy->getGameType() == kGameTypeNancy8 && (sceneId >= 5420 && sceneId <= 5422)))
+				g_nancy->setMouseEnabled(true);
 		}
 
 		break;


Commit: 79519ca67096ad671ea21a5c5ee807d302c75bb3
    https://github.com/scummvm/scummvm/commit/79519ca67096ad671ea21a5c5ee807d302c75bb3
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-01T04:15:47+03:00

Commit Message:
NANCY: NANCY12: Implement the Set3DSoundListenerPosition AR

Changed paths:
    engines/nancy/action/soundrecords.cpp
    engines/nancy/sound.cpp
    engines/nancy/sound.h
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 7725cfa7b8a..c9d91b2651f 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -106,7 +106,7 @@ void Set3DSoundListenerPosition::readData(Common::SeekableReadStream &stream) {
 }
 
 void Set3DSoundListenerPosition::execute() {
-	// TODO: forward the listener position to the sound manager.
+	g_nancy->_sound->setListenerPosition(Math::Vector3d(_posX, _posY, _posZ));
 	_isDone = true;
 }
 
diff --git a/engines/nancy/sound.cpp b/engines/nancy/sound.cpp
index 6be6300ae40..f7ac6110013 100644
--- a/engines/nancy/sound.cpp
+++ b/engines/nancy/sound.cpp
@@ -574,6 +574,24 @@ void SoundManager::update3DSoundMaxDistance(uint16 channelID, uint32 maxDistance
 	_shouldRecalculate = true;
 }
 
+void SoundManager::setListenerPosition(const Math::Vector3d &position) {
+	_listenerPositionOverride = position;
+	_hasListenerPositionOverride = true;
+	_shouldRecalculate = true;
+}
+
+void SoundManager::clearListenerPositionOverride() {
+	_hasListenerPositionOverride = false;
+}
+
+Math::Vector3d SoundManager::getListenerPosition() const {
+	if (_hasListenerPositionOverride) {
+		return _listenerPositionOverride;
+	}
+
+	return NancySceneState.getSceneSummary().listenerPosition;
+}
+
 uint32 SoundManager::getRate(uint16 channelID) {
 	if (channelID >= _channels.size())
 		return 0;
@@ -737,14 +755,15 @@ SoundManager::Channel::~Channel() {
 
 void SoundManager::soundEffectMaintenance() {
 	// Interpolate position and rotation when scene has changed to avoid audible chop in sound
-	if (_position != NancySceneState.getSceneSummary().listenerPosition && _positionLerp == 0) {
+	Math::Vector3d listenerPosition = getListenerPosition();
+	if (_position != listenerPosition && _positionLerp == 0) {
 		++_positionLerp;
 	}
 
 	if (_positionLerp > 1) {
 		++_positionLerp;
 		if (_positionLerp > 10) {
-			_position = NancySceneState.getSceneSummary().listenerPosition;
+			_position = listenerPosition;
 			_positionLerp = 0;
 		}
 	}
@@ -882,7 +901,7 @@ void SoundManager::soundEffectMaintenance(uint16 channelID, bool force) {
 			(chan.playCommands & ~kPlaySequential) & (kPlaySequentialFrameAnchor | kPlayRandomPosition | kPlayMoveLinear)) {
 
 		// Interpolate position when we've changed scenes
-		Math::Vector3d listenerPos = Math::Vector3d::interpolate(_position, NancySceneState.getSceneSummary().listenerPosition, (float)_positionLerp / 10.0);
+		Math::Vector3d listenerPos = Math::Vector3d::interpolate(_position, getListenerPosition(), (float)_positionLerp / 10.0);
 		float dist = listenerPos.getDistanceTo(chan.position);
 		float volume;
 
diff --git a/engines/nancy/sound.h b/engines/nancy/sound.h
index 50f35796a54..f195e10c42a 100644
--- a/engines/nancy/sound.h
+++ b/engines/nancy/sound.h
@@ -100,6 +100,12 @@ public:
 	void update3DSoundMinDistance(uint16 channelID, uint32 minDistance);
 	void update3DSoundMaxDistance(uint16 channelID, uint32 maxDistance);
 
+	// Nancy12 Set3DSoundListenerPosition (AR 168). Overrides the 3D listener
+	// position, which is otherwise taken from the scene summary. The override
+	// is cleared when a new scene is loaded.
+	void setListenerPosition(const Math::Vector3d &position);
+	void clearListenerPositionOverride();
+
 	uint32 getRate(uint16 channelID);
 	uint32 getRate(const SoundDescription &description);
 	uint32 getRate(const Common::String &chunkName);
@@ -154,6 +160,10 @@ protected:
 
 	void soundEffectMaintenance(uint16 channelID, bool force = false);
 
+	// Returns the listener position override if one is active, otherwise
+	// the scene summary's listener position
+	Math::Vector3d getListenerPosition() const;
+
 	Audio::Mixer *_mixer;
 
 	Common::Array<Channel> _channels;
@@ -164,6 +174,9 @@ protected:
 	Math::Vector3d _orientation;
 	Math::Vector3d _position;
 	uint _positionLerp = 0;
+
+	Math::Vector3d _listenerPositionOverride;
+	bool _hasListenerPositionOverride = false;
 };
 
 } // End of namespace Nancy
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index d4c4b0221d9..fe498a1b219 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1131,6 +1131,7 @@ void Scene::load(bool fromSaveFile) {
 	_inventorySoundOverrides.clear();
 
 	_timers.sceneTime = 0;
+	g_nancy->_sound->clearListenerPositionOverride();
 	g_nancy->_sound->recalculateSoundEffects();
 
 	// Increment the number of times we've visited this scene, unless we're




More information about the Scummvm-git-logs mailing list