[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