[Scummvm-git-logs] scummvm master -> abf147b0d18fe4a61618c9ce215c0ea2c62eca6c
bluegr
noreply at scummvm.org
Tue Aug 4 01:00:38 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
0e966b9b1b NANCY: NANCY12: Fix TurningPuzzle
61ddf21f64 NANCY: NANCY12: Implement new functionality for SafeDialPuzzle
abf147b0d1 NANCY: NANCY9: Fixes for OneBuildPuzzle
Commit: 0e966b9b1bed8e8d98fc70d04944df9794e83701
https://github.com/scummvm/scummvm/commit/0e966b9b1bed8e8d98fc70d04944df9794e83701
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-04T04:00:21+03:00
Commit Message:
NANCY: NANCY12: Fix TurningPuzzle
The 3 new bytes in Nancy12 are placed before the spindle data, not
afterwards. Fixes the Coach House door puzzle.
Changed paths:
engines/nancy/action/puzzle/turningpuzzle.cpp
diff --git a/engines/nancy/action/puzzle/turningpuzzle.cpp b/engines/nancy/action/puzzle/turningpuzzle.cpp
index a0013c1acdd..53c3c675516 100644
--- a/engines/nancy/action/puzzle/turningpuzzle.cpp
+++ b/engines/nancy/action/puzzle/turningpuzzle.cpp
@@ -366,17 +366,17 @@ void TurningPuzzle::readData(Common::SeekableReadStream &stream) {
_turnSound.readNormal(stream);
+ if (g_nancy->getGameType() >= kGameTypeNancy12) {
+ // Nancy 12 inserts 3 bytes before the correct order; purpose unknown.
+ stream.skip(3);
+ }
+
_correctOrder.resize(numSpindles);
for (uint i = 0; i < numSpindles; ++i) {
_correctOrder[i] = stream.readUint16LE();
}
stream.skip((16 - numSpindles) * 2);
- if (g_nancy->getGameType() >= kGameTypeNancy12) {
- // Nancy 12 inserts 3 bytes here (zero in the samples seen); purpose unknown.
- stream.skip(3);
- }
-
_solveScene.readData(stream);
_solveSoundDelay = stream.readUint16LE();
_solveSound.readNormal(stream);
Commit: 61ddf21f649ae7faa2c8f4b4db7e4a98cade56f4
https://github.com/scummvm/scummvm/commit/61ddf21f649ae7faa2c8f4b4db7e4a98cade56f4
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-04T04:00:22+03:00
Commit Message:
NANCY: NANCY12: Implement new functionality for SafeDialPuzzle
Fixes the alarm clock puzzle
Changed paths:
engines/nancy/action/puzzle/safedialpuzzle.cpp
engines/nancy/action/puzzle/safedialpuzzle.h
engines/nancy/cursor.cpp
engines/nancy/cursor.h
diff --git a/engines/nancy/action/puzzle/safedialpuzzle.cpp b/engines/nancy/action/puzzle/safedialpuzzle.cpp
index 20de6afa520..435c56a11af 100644
--- a/engines/nancy/action/puzzle/safedialpuzzle.cpp
+++ b/engines/nancy/action/puzzle/safedialpuzzle.cpp
@@ -134,6 +134,10 @@ void SafeDialPuzzle::readData(Common::SeekableReadStream &stream) {
Common::Rect temp = _cwHotspot;
_cwHotspot = _ccwHotspot;
_ccwHotspot = temp;
+ } else if (g_nancy->getGameType() >= kGameTypeNancy12) {
+ // Nancy 12's dial frames are laid out counter-clockwise, so the counter-clockwise
+ // hotspot is the one that steps forwards through them
+ _ccwTurnsForwards = true;
}
_spinSound.readNormal(stream);
@@ -217,48 +221,28 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
return;
} else if (NancySceneState.getViewport().convertViewportToScreen(_ccwHotspot).contains(input.mousePos)) {
- if (!_enableWraparound && _current == 0) {
+ if (!canTurnDial(_ccwTurnsForwards)) {
return;
}
- g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveLeft : CursorManager::kRotateCCW);
+ g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveLeft : CursorManager::kDialCCW);
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
_animState != kReset && _animState != kResetAnim) {
- if (_current == 0) {
- _current = _dialSrcs.size() / (1 + _numInbetweens) - 1;
- } else {
- --_current;
- }
-
- drawDialFrame(_current * (1 + _numInbetweens) + (_numInbetweens ? 1 : 0));
- _nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
-
- g_nancy->_sound->playSound(_spinSound);
- _animState = kSpin;
+ turnDial(_ccwTurnsForwards);
}
return;
} else if (NancySceneState.getViewport().convertViewportToScreen(_cwHotspot).contains(input.mousePos)) {
- if (!_enableWraparound && _current == (_dialSrcs.size() / (1 + _numInbetweens) - 1)) {
+ if (!canTurnDial(!_ccwTurnsForwards)) {
return;
}
- g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveRight : CursorManager::kRotateCW);
+ g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveRight : CursorManager::kDialCW);
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
_animState != kReset && _animState != kResetAnim) {
- drawDialFrame(_current * (1 + _numInbetweens) + 1);
- _nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
-
- if (_current == (_dialSrcs.size() / (1 + _numInbetweens)) - 1) {
- _current = 0;
- } else {
- ++_current;
- }
-
- g_nancy->_sound->playSound(_spinSound);
- _animState = kSpin;
+ turnDial(!_ccwTurnsForwards);
}
return;
@@ -268,8 +252,12 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
return;
}
+ // The puzzle arrow cursor only exists in Nancy 10 and up
+ CursorManager::CursorType buttonCursor = g_nancy->getGameType() >= kGameTypeNancy10 ?
+ CursorManager::kPuzzleArrow : CursorManager::kHotspot;
+
if (NancySceneState.getViewport().convertViewportToScreen(_arrowDest).contains(input.mousePos)) {
- g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ g_nancy->_cursor->setCursorType(buttonCursor);
if (!g_nancy->_sound->isSoundPlaying(_selectSound) && input.input & NancyInput::kLeftMouseButtonUp) {
g_nancy->_sound->playSound(_selectSound);
@@ -282,7 +270,7 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
return;
} else if (NancySceneState.getViewport().convertViewportToScreen(_resetDest).contains(input.mousePos)) {
- g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ g_nancy->_cursor->setCursorType(buttonCursor);
if (!g_nancy->_sound->isSoundPlaying(_resetSound) && input.input & NancyInput::kLeftMouseButtonUp) {
_drawSurface.blitFrom(_image1, _resetSrc, _resetDest);
@@ -298,6 +286,30 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
}
}
+bool SafeDialPuzzle::canTurnDial(bool forwards) const {
+ if (_enableWraparound) {
+ return true;
+ }
+
+ return forwards ? _current != numDialPositions() - 1 : _current != 0;
+}
+
+void SafeDialPuzzle::turnDial(bool forwards) {
+ // The in-between frame of a step always belongs to the lower of the two positions
+ if (forwards) {
+ drawDialFrame(_current * (1 + _numInbetweens) + 1);
+ _current = (_current == numDialPositions() - 1) ? 0 : _current + 1;
+ } else {
+ _current = (_current == 0) ? numDialPositions() - 1 : _current - 1;
+ drawDialFrame(_current * (1 + _numInbetweens) + (_numInbetweens ? 1 : 0));
+ }
+
+ _nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
+
+ g_nancy->_sound->playSound(_spinSound);
+ _animState = kSpin;
+}
+
void SafeDialPuzzle::drawDialFrame(uint frame) {
if (frame >= _dialSrcs.size() / 2 && !_imageName2.empty()) {
_drawSurface.blitFrom(_image2, _dialSrcs[frame], _dialDest);
@@ -309,9 +321,10 @@ void SafeDialPuzzle::drawDialFrame(uint frame) {
}
void SafeDialPuzzle::pushSequence(uint id) {
- if (!_useMoveArrows && id != 0) {
- // When the puzzle is set to use rotation cursors, the ids in the correct sequence are in reverse order
- id = (_dialSrcs.size() / (1 + _numInbetweens)) - id;
+ if (!_useMoveArrows && id != 0 && g_nancy->getGameType() < kGameTypeNancy12) {
+ // When the puzzle is set to use rotation cursors, the ids in the correct sequence are in reverse order.
+ // Nancy 12 stores the dial positions themselves.
+ id = numDialPositions() - id;
}
_playerSequence.push_back(id);
diff --git a/engines/nancy/action/puzzle/safedialpuzzle.h b/engines/nancy/action/puzzle/safedialpuzzle.h
index 910bebf8905..7c775fd37b4 100644
--- a/engines/nancy/action/puzzle/safedialpuzzle.h
+++ b/engines/nancy/action/puzzle/safedialpuzzle.h
@@ -49,6 +49,9 @@ protected:
void drawDialFrame(uint frame);
void pushSequence(uint id);
+ uint numDialPositions() const { return _dialSrcs.size() / (1 + _numInbetweens); }
+ bool canTurnDial(bool forwards) const;
+ void turnDial(bool forwards);
Common::Path _imageName1;
Common::Path _imageName2;
@@ -75,6 +78,7 @@ protected:
Common::Rect _cwHotspot;
bool _useMoveArrows = false;
+ bool _ccwTurnsForwards = false;
SoundDescription _spinSound;
SoundDescription _selectSound;
diff --git a/engines/nancy/cursor.cpp b/engines/nancy/cursor.cpp
index e628451c762..24ec422d61d 100644
--- a/engines/nancy/cursor.cpp
+++ b/engines/nancy/cursor.cpp
@@ -177,6 +177,8 @@ uint CursorManager::resolveNancy10CursorID(CursorType type, int16 itemID, bool s
case kMoveDown: return kNewMoveDown;
case kRotateCW: return kNewRotateCW;
case kRotateCCW: return kNewRotateCCW;
+ case kDialCW: return kNewDialCW;
+ case kDialCCW: return kNewDialCCW;
case kRotateRight: return kNewRotateRight;
case kRotateLeft: return kNewRotateLeft;
case kInvertedRotateRight: return kNewInvertedRotateRight;
@@ -233,9 +235,11 @@ uint CursorManager::resolveNancy13CursorID(CursorType type, int16 itemID, bool s
case kMoveRight: sysType = kNancy13MoveRight; break;
case kRotateCW:
case kRotateRight:
+ case kDialCW:
case kInvertedRotateRight: sysType = kNancy13RotateCW; break;
case kRotateCCW:
case kRotateLeft:
+ case kDialCCW:
case kInvertedRotateLeft: sysType = kNancy13RotateCCW; break;
case kDragHand:
case kDropHand: sysType = kNancy13DropHand; break;
@@ -356,9 +360,11 @@ void CursorManager::setCursor(CursorType type, int16 itemID, bool setFromScript,
}
break;
case kRotateCW:
+ case kDialCW:
_curCursorID = kRotateCW;
return;
case kRotateCCW:
+ case kDialCCW:
_curCursorID = kRotateCCW;
return;
default:
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index 5f00f938b29..f8824feb022 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -59,6 +59,8 @@ public:
kDragHand = 23, // Hand cursor used when dragging an item (Nancy 10+)
kDropHand = 24, // Drop-hand cursor used while a piece is held over a target (Nancy 10+)
kPuzzleArrow = 25, // Puzzle arrow cursor shown when hovering a clickable puzzle hotspot (Nancy 10+)
+ kDialCW = 26, // Dial turn cursors used by SafeDialPuzzle (Nancy 10+); the older
+ kDialCCW = 27, // games use kRotateCW / kRotateCCW instead
// Cursors in Nancy10 and newer games. The CURS chunk holds 37 system
// cursor types in pairs; type T's idle slot is (T*2) and its hotspot
@@ -90,6 +92,8 @@ public:
kNewInvertedRotateLeft = 34, // Type 17 â Inverted 360 rotation
kNewUseHand = 36, // Type 18 â Hand used while using items
kNewDragHand = 38, // Type 19 â Hand used while dragging puzzle pieces (e.g. SortPuzzle pickup action sets this)
+ kNewDialCW = 41, // Type 20 hotspot â Dial turn cursors, used by SafeDialPuzzle
+ kNewDialCCW = 43, // Type 21 hotspot
kNewPuzzleArrow = 45, // Type 22 hotspot â Arrow cursor shown when hovering a clickable puzzle hotspot
kNewPuzzleSlideUp = 47, // Type 23 hotspot â Slide-arrow shown over a movable tile in CollisionPuzzle/TileMovePuzzle
kNewPuzzleSlideDown = 49, // Type 24 hotspot
Commit: abf147b0d18fe4a61618c9ce215c0ea2c62eca6c
https://github.com/scummvm/scummvm/commit/abf147b0d18fe4a61618c9ce215c0ea2c62eca6c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-04T04:00:26+03:00
Commit Message:
NANCY: NANCY9: Fixes for OneBuildPuzzle
- Fixes rotated piece rendering in Nancy9 pipe puzzle
- Fixes rotation check in Nancy12 domino puzzle
Changed paths:
engines/nancy/action/puzzle/onebuildpuzzle.cpp
engines/nancy/action/puzzle/onebuildpuzzle.h
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.cpp b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
index 1a5d0bc6603..13a56f960d9 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.cpp
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
@@ -73,8 +73,9 @@ void OneBuildPuzzle::init() {
p.rotateSurfaces[0].blitFrom(_image, p.srcRect, Common::Point(0, 0));
p.hasSurface[0] = true;
- // Rotations 1-3: only needed if pieces can rotate
- if (_canRotateAll || p.isPreRotated) {
+ // Rotations 1-3: only needed if pieces can rotate, or if this one doesn't
+ // start upright. Pre-placed pieces never rotate and stay at rotation 0.
+ if ((_canRotateAll || p.defaultRotation != 0) && !p.isPreRotated) {
for (int r = 1; r < 4; ++r) {
rotateSurface90CW(p.rotateSurfaces[r - 1], p.rotateSurfaces[r]);
p.rotateSurfaces[r].setTransparentColor(_drawSurface.getTransparentColor());
@@ -222,8 +223,8 @@ void OneBuildPuzzle::readDataNancy12(Common::SeekableReadStream &stream) {
readRect(stream, p.slotRect);
readRect(stream, p.homeRect);
p.defaultRotation = stream.readByte();
- // A piece is pre-placed only when this marker is exactly 10.
- p.isPreRotated = stream.readByte() == 10;
+ p.requiredRotation = stream.readByte();
+ p.isPreRotated = p.requiredRotation == kPrePlacedRotation;
}
// Optional placement-order arrays, each present only when its flag is set.
@@ -235,9 +236,9 @@ void OneBuildPuzzle::readDataNancy12(Common::SeekableReadStream &stream) {
}
if (stream.readByte() != 0) {
- _legacyPlacementOrder.resize(_numPieces);
+ _preplacedZOrder.resize(_numPieces);
for (uint i = 0; i < _numPieces; ++i)
- _legacyPlacementOrder[i] = stream.readSint16LE();
+ _preplacedZOrder[i] = stream.readSint16LE();
}
}
@@ -257,20 +258,19 @@ void OneBuildPuzzle::readData(Common::SeekableReadStream &stream) {
stream.skip(6); // rotationMode, zoneHeight, zoneWidth, mouse-clamping flag
_slotTolerance = stream.readSint16LE();
- if (isNancy10) {
- // TODO: purpose of this duplicate placement-order block is unknown.
- _legacyOrderedFlag = stream.readByte() != 0;
- _legacyPlacementOrder.resize(20);
- for (uint i = 0; i < 20; ++i)
- _legacyPlacementOrder[i] = stream.readSint16LE();
- }
-
- _orderedPlacement = stream.readByte();
+ _orderedPlacement = stream.readByte() != 0;
_placementOrder.resize(20);
for (uint i = 0; i < 20; ++i)
_placementOrder[i] = stream.readSint16LE();
+ if (isNancy10) {
+ stream.readByte(); // Set when the puzzle stacks its pre-placed pieces
+ _preplacedZOrder.resize(20);
+ for (uint i = 0; i < 20; ++i)
+ _preplacedZOrder[i] = stream.readSint16LE();
+ }
+
// Nancy 10 piece records add an alternative source rect at the front.
const uint pieceSize = isNancy10 ? 66 : 50;
@@ -298,7 +298,9 @@ void OneBuildPuzzle::readData(Common::SeekableReadStream &stream) {
readRect(stream, p.slotRect);
readRect(stream, p.homeRect);
p.defaultRotation = stream.readByte();
- p.isPreRotated = stream.readByte();
+ // Up to Nancy 11 this byte is a plain pre-placed flag, and a piece only
+ // ever fits its slot upright; requiredRotation stays at 0.
+ p.isPreRotated = stream.readByte() != 0;
}
if (isNancy10) {
@@ -486,10 +488,10 @@ void OneBuildPuzzle::handleInput(NancyInput &input) {
piece.gameRect.right <= slot.right + _slotTolerance &&
piece.gameRect.bottom <= slot.bottom + _slotTolerance);
- // A piece only fits at its correct (unrotated) orientation; a
+ // A piece only fits at the orientation its slot calls for; a
// 180-degree flip keeps the same bounding box, so proximity alone
// would accept an upside-down piece.
- bool rotationOk = (piece.curRotation == 0);
+ bool rotationOk = (piece.curRotation == piece.requiredRotation);
bool orderOk = !_orderedPlacement ||
(_piecesPlaced < (uint16)_placementOrder.size() &&
@@ -648,7 +650,7 @@ void OneBuildPuzzle::updatePieceRender(int pieceIdx) {
void OneBuildPuzzle::rotatePiece(int pieceIdx) {
Piece &p = _pieces[pieceIdx];
- if (!_canRotateAll && !p.isPreRotated)
+ if (p.isPreRotated || (!_canRotateAll && p.defaultRotation == 0))
return;
int oldRot = p.curRotation;
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.h b/engines/nancy/action/puzzle/onebuildpuzzle.h
index f467ce45e92..8e8e6b9e31a 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.h
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.h
@@ -51,6 +51,11 @@ public:
protected:
Common::String getRecordTypeName() const override { return "OneBuildPuzzle"; }
+ // Nancy 12 repurposed the piece's trailing flag byte into the rotation the
+ // piece must be in to be accepted, with this value standing in for the
+ // pre-placed flag it used to be.
+ static const uint8 kPrePlacedRotation = 10;
+
struct Piece : RenderObject {
Piece() : RenderObject(0) {}
@@ -60,6 +65,9 @@ protected:
Common::Rect slotRect;
Common::Rect homeRect;
uint8 defaultRotation = 0;
+ // Rotation the piece must be in to be accepted into its slot. Always 0
+ // before Nancy 12, which is why older games only accept upright pieces.
+ uint8 requiredRotation = 0;
bool isPreRotated = false;
// Runtime
@@ -67,7 +75,7 @@ protected:
int curRotation = 0;
bool placed = false;
- // Rotations 1-3 only built when canRotateAll or isPreRotated
+ // Rotations 1-3 only built when canRotateAll or the piece starts rotated
Graphics::ManagedSurface rotateSurfaces[4];
bool hasSurface[4] = {};
@@ -89,11 +97,11 @@ protected:
bool _orderedPlacement = false; // Pieces must be placed in a specific order
Common::Array<int16> _placementOrder; // 1-indexed piece IDs in required placement order
- // --- Nancy 10 additions ---
+ // Stacking order of the pieces that start out already placed, 1-indexed.
+ // TODO: not applied yet; pre-placed pieces keep their array order.
+ Common::Array<int16> _preplacedZOrder;
- // TODO: runtime role unknown; parsed for round-trip but not consumed.
- bool _legacyOrderedFlag = false;
- Common::Array<int16> _legacyPlacementOrder;
+ // --- Nancy 10 additions ---
// Filename only (no SoundDescription metadata).
Common::String _extraSoundName;
More information about the Scummvm-git-logs
mailing list