[Scummvm-git-logs] scummvm master -> 946eaa3c1e9e3a9d580e7970e214fecb0462ba1a
bluegr
noreply at scummvm.org
Wed Jun 17 01:06:44 UTC 2026
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
e41e7bde85 NANCY: Handle more cases in onebuildpuzzle
e27150898a naNCY: Adjustments to the quiz puzzle
6bf575df25 NANCY: Add a sanity check in setItemDisabledState()
6039a72de3 NANCY: Fix text display in the cell phone screens in Nancy 10
10de11d8c0 NANCY: Fix wall collision in the magnet maze puzzle in Nancy 10
459c286cd8 NANCY: Initial work on the PlayRandomMovie ARs for Nancy 11
946eaa3c1e NANCY: Handle alt surfaces in onebuildpuzzle
Commit: e41e7bde857b96962b075fae5dfab8de1c093ec3
https://github.com/scummvm/scummvm/commit/e41e7bde857b96962b075fae5dfab8de1c093ec3
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:25+03:00
Commit Message:
NANCY: Handle more cases in onebuildpuzzle
- Ignore piece rotation regarding piece placement, like the original
- Skip pre-placed pieces
Fixes the bridle puzzle in Nancy 10
Changed paths:
engines/nancy/action/puzzle/onebuildpuzzle.cpp
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.cpp b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
index fd1f080afab..c10539ecda2 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.cpp
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
@@ -317,23 +317,29 @@ void OneBuildPuzzle::handleInput(NancyInput &input) {
Common::Rect slot = piece.slotRect;
- // Correct placement: bounding-box must fit within slot +- tolerance
- // (piece is rotation 0, same size as slot)
+ // Bounding-box must fit within slot +- tolerance. The original
+ // engine doesn't check rotation separately; a rotated piece's
+ // dimensions are reflected in gameRect, so a non-fitting rotation
+ // is rejected by the rect inequalities below.
bool nearSlot = (piece.gameRect.left >= slot.left - _slotTolerance &&
piece.gameRect.top >= slot.top - _slotTolerance &&
piece.gameRect.right <= slot.right + _slotTolerance &&
piece.gameRect.bottom <= slot.bottom + _slotTolerance);
- bool correctRotation = (piece.curRotation == 0);
bool orderOk = !_orderedPlacement ||
(_piecesPlaced < (uint16)_placementOrder.size() &&
_placementOrder[_piecesPlaced] == (int16)(_pickedUpPiece + 1));
- if (nearSlot && correctRotation && orderOk) {
+ if (nearSlot && orderOk) {
piece.gameRect = piece.slotRect;
piece.placed = true;
_correctlyPlaced = true;
++_piecesPlaced;
+
+ // Skip pre-placed pieces
+ if (_piecesPlaced < _placementOrder.size() && _placementOrder[_piecesPlaced] - 1 < _pieces.size())
+ if (_pieces[_placementOrder[_piecesPlaced] - 1].isPreRotated)
+ ++_piecesPlaced;
} else {
_correctlyPlaced = false;
if (!_freePlacement) {
Commit: e27150898aa8446b93050a24eb3bd43527c1dcde
https://github.com/scummvm/scummvm/commit/e27150898aa8446b93050a24eb3bd43527c1dcde
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:26+03:00
Commit Message:
naNCY: Adjustments to the quiz puzzle
- Bump maximum input boxes to 13 (used in Nancy 10)
- Implement the exit hotspot for this puzzle
Fixes the jail tally marks puzzle in Nancy 10
Changed paths:
engines/nancy/action/puzzle/quizpuzzle.cpp
engines/nancy/action/puzzle/quizpuzzle.h
diff --git a/engines/nancy/action/puzzle/quizpuzzle.cpp b/engines/nancy/action/puzzle/quizpuzzle.cpp
index 2a3ffcdc6a9..ffbd03a193c 100644
--- a/engines/nancy/action/puzzle/quizpuzzle.cpp
+++ b/engines/nancy/action/puzzle/quizpuzzle.cpp
@@ -138,7 +138,7 @@ void QuizPuzzle::readDataOld(Common::SeekableReadStream &stream) {
// 0x32 49 doneSound (readNormal)
// 0x63 30 done subtitle (skip)
// 0x81 25 cancelScene
-// 0x9A 16 unknown (skip)
+// 0x9A 16 exitHotspot rect (4Ãsint32)
// 0xAA 2 correctSoundChannel
// 0xAC 2 wrongSoundChannel
// 0xAE 1 skipEmptyOnEnter flag
@@ -172,7 +172,7 @@ void QuizPuzzle::readDataNew(Common::SeekableReadStream &stream) {
_doneText = readSubtitle(stream);
_cancelScene.readData(stream);
- stream.skip(16); // unknown
+ readRect(stream, _exitHotspot);
_correctSoundChannel = stream.readUint16LE();
_wrongSoundChannel = stream.readUint16LE();
@@ -586,6 +586,19 @@ void QuizPuzzle::handleInput(NancyInput &input) {
char cursorChar = (g_nancy->getGameType() == kGameTypeNancy8) ? '-' : _cursorChar;
+ // Nancy 9+: exit hotspot at chunk +0x9A. Click cancels the puzzle.
+ if (g_nancy->getGameType() != kGameTypeNancy8 && !_exitHotspot.isEmpty()) {
+ Common::Rect exitScreen = NancySceneState.getViewport().convertViewportToScreen(_exitHotspot);
+ if (exitScreen.contains(input.mousePos)) {
+ g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _cancelled = true;
+ _state = kActionTrigger;
+ }
+ return;
+ }
+ }
+
// Hover over an unsolved text box: show the hotspot cursor and, on click,
// move the typing focus to that box.
for (uint i = 0; i < _numBoxes; ++i) {
diff --git a/engines/nancy/action/puzzle/quizpuzzle.h b/engines/nancy/action/puzzle/quizpuzzle.h
index 662157d492e..9d5371ebddb 100644
--- a/engines/nancy/action/puzzle/quizpuzzle.h
+++ b/engines/nancy/action/puzzle/quizpuzzle.h
@@ -47,7 +47,7 @@ protected:
Common::String getRecordTypeName() const override { return "QuizPuzzle"; }
private:
- static const int kMaxBoxes = 8;
+ static const int kMaxBoxes = 13;
// Format-specific read/execute implementations
void readDataOld(Common::SeekableReadStream &stream); // Nancy 8
@@ -75,7 +75,7 @@ private:
// Per-box answer data: up to 3 valid answers (case-insensitive match), plus
// an optional event flag to set when that box is answered correctly.
Common::String _answers[kMaxBoxes][3];
- int16 _answerFlags[kMaxBoxes] = { -1, -1, -1, -1, -1, -1, -1, -1 };
+ int16 _answerFlags[kMaxBoxes] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
SoundDescription _correctSound; // Nancy 8: global correct sound
Common::String _correctText;
@@ -87,6 +87,7 @@ private:
SceneChangeWithFlag _solveScene; // scene to go to when all boxes are solved
SceneChangeWithFlag _cancelScene; // scene to go to on cancel
+ Common::Rect _exitHotspot; // Nancy 9+: viewport-relative cancel hotspot
// ---- Data (Nancy 9+) ----
char _cursorChar = '-'; // cursor character (configurable in Nancy 9)
Commit: 6bf575df250b31c454667a4bcd343ba7da5be5ca
https://github.com/scummvm/scummvm/commit/6bf575df250b31c454667a4bcd343ba7da5be5ca
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:28+03:00
Commit Message:
NANCY: Add a sanity check in setItemDisabledState()
Fixes a crash when the villain chase scene starts in Nancy 10
Changed paths:
engines/nancy/state/scene.h
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index e9118c381ea..0a580ab87dc 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -136,7 +136,10 @@ public:
void setNoHeldItem();
byte hasItem(int16 id) const;
byte getItemDisabledState(int16 id) const { return _flags.disabledItems[id]; }
- void setItemDisabledState(int16 id, byte state) { _flags.disabledItems[id] = state; }
+ void setItemDisabledState(int16 id, byte state) {
+ if (id < _flags.disabledItems.size())
+ _flags.disabledItems[id] = state;
+ }
void installInventorySoundOverride(byte command, const SoundDescription &sound, const Common::String &caption, uint16 itemID);
void playItemCantSound(int16 itemID = -1, bool notHoldingSound = false);
Commit: 6039a72de3a40d89bb7f9b612087bdb9e8f85b35
https://github.com/scummvm/scummvm/commit/6039a72de3a40d89bb7f9b612087bdb9e8f85b35
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:31+03:00
Commit Message:
NANCY: Fix text display in the cell phone screens in Nancy 10
Also, show the selection indicator in the Internet browser screen
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index a283748313d..a54f29f84a6 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -763,8 +763,13 @@ void CellPhonePopup::drawLinkList() {
rowText.erase(rowText.find("<n>"), 3);
}
+ // Original anchors row text on the row's bottom (baseline-up
+ // rendering); mirror that so the glyphs sit at the bottom of
+ // the row instead of glued to the top.
+ const int textY = MAX<int16>(rowRect.top,
+ rowRect.bottom - font->getFontHeight());
font->drawString(&_drawSurface, rowText,
- textX, rowRect.top,
+ textX, textY,
rowRect.right - textX, 0);
}
}
@@ -952,8 +957,10 @@ void CellPhonePopup::drawDirectoryList() {
++visited;
const Common::Rect rowRect = directoryRowRect(visibleRow);
+ const int textY = MAX<int16>(rowRect.top,
+ rowRect.bottom - font->getFontHeight());
font->drawString(&_drawSurface, c.name,
- rowRect.left, rowRect.top,
+ rowRect.left, textY,
rowRect.width(), 0);
++visibleRow;
}
@@ -1026,10 +1033,10 @@ void CellPhonePopup::drawDirectoryArrows() {
}
// Selection indicator (dirArrowSrc sprite) at the dirCursorSrc column,
- // stepped down by the active entry's layout row â only meaningful in
- // directory mode; list and content modes have their own arrow logic
- // (or none at all) and a stray cursor sprite paints over their LCD.
- if (_screenState != kDirectory) {
+ // stepped down by the active entry's layout row. Drawn for directory
+ // and the search-topic list; the email list signals the current row
+ // by swapping its per-row icon, so no separate arrow there.
+ if (_screenState != kDirectory && _screenState != kWebList) {
return;
}
const Common::Rect &arrowSrc = _uiclData->dirArrowSrc;
Commit: 10de11d8c06f98265c6082ec66286a6ab085837a
https://github.com/scummvm/scummvm/commit/10de11d8c06f98265c6082ec66286a6ab085837a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:31+03:00
Commit Message:
NANCY: Fix wall collision in the magnet maze puzzle in Nancy 10
Changed paths:
engines/nancy/action/puzzle/magnetmazepuzzle.cpp
diff --git a/engines/nancy/action/puzzle/magnetmazepuzzle.cpp b/engines/nancy/action/puzzle/magnetmazepuzzle.cpp
index 710eb2f5263..50c623adf51 100644
--- a/engines/nancy/action/puzzle/magnetmazepuzzle.cpp
+++ b/engines/nancy/action/puzzle/magnetmazepuzzle.cpp
@@ -221,7 +221,14 @@ bool MagnetMazePuzzle::collidesAt(const Common::Rect &r) const {
if (r.left < 0 || r.top < 0 || r.right > mazeW || r.bottom > mazeH)
return true;
- const uint32 wall = _mazeImage.format.RGBToColor(_wallColorR, _wallColorG, _wallColorB);
+ // The chunk stores each wall channel as a 5-bit value (the original engine
+ // masks with 0x1f and packs directly into a 16-bit display-format word).
+ // Scale to 8-bit before RGBToColor so the resulting packed value matches
+ // the actual maze-image pixels for the current pixel format.
+ const byte r8 = (byte)((_wallColorR & 0x1f) << 3);
+ const byte g8 = (byte)((_wallColorG & 0x1f) << 3);
+ const byte b8 = (byte)((_wallColorB & 0x1f) << 3);
+ const uint32 wall = _mazeImage.format.RGBToColor(r8, g8, b8);
for (int y = y0; y < y1; ++y) {
for (int x = x0; x < x1; ++x) {
if (_mazeImage.getPixel(x, y) == wall)
Commit: 459c286cd8f59c757d1b003cdd0f2d7e0bf6f23e
https://github.com/scummvm/scummvm/commit/459c286cd8f59c757d1b003cdd0f2d7e0bf6f23e
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:33+03:00
Commit Message:
NANCY: Initial work on the PlayRandomMovie ARs for Nancy 11
Used in cases such as the conversation with the parrot. Conversation
videos are still not played in this case, as there's no appropriate
trigger in the conversation scene scripts
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/secondarymovie.cpp
engines/nancy/action/secondarymovie.h
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index d89f822b735..c0efd860108 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -187,12 +187,10 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
case 32:
// Nancy 10+
return new UIPopupPrepScene();
- case 45: // Nancy11
- warning("PlayRandomMovie"); // TODO
- return nullptr;
+ case 45: // Nancy11 - random-movie variant of PlaySecondaryMovie
+ return new PlaySecondaryMovie(true);
case 46: // Nancy11
- warning("PlayRandomMovieControl"); // TODO
- return nullptr;
+ return new PlayRandomMovieControl();
case 40:
if (g_nancy->getGameType() <= kGameTypeNancy1)
return new LightningOn(); // Only used in TVD
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 5417d3d7772..8ac8ae5a835 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -28,6 +28,7 @@
#include "engines/nancy/action/secondarymovie.h"
#include "engines/nancy/state/scene.h"
+#include "common/random.h"
#include "common/serializer.h"
#include "video/bink_decoder.h"
@@ -45,10 +46,85 @@ PlaySecondaryMovie::~PlaySecondaryMovie() {
}
}
+void PlaySecondaryMovie::readRandomSequence(Common::Serializer &ser, RandomSequence &seq) {
+ readFilename(ser, seq.name);
+ ser.syncAsUint16LE(seq.startFrame);
+ ser.syncAsUint16LE(seq.unknown_0x23);
+ ser.syncAsSint32LE(seq.minPauseMs);
+ ser.syncAsSint32LE(seq.maxPauseMs);
+ ser.syncAsUint16LE(seq.unknown_0x2D);
+
+ uint16 nextCount = 0;
+ ser.syncAsUint16LE(nextCount);
+
+ seq.nextSequences.resize(nextCount);
+ for (uint i = 0; i < nextCount; ++i) {
+ readFilename(ser, seq.nextSequences[i].name);
+ ser.syncAsUint16LE(seq.nextSequences[i].unknown);
+ }
+}
+
+void PlaySecondaryMovie::readRandomMovieData(Common::Serializer &ser, Common::SeekableReadStream &stream) {
+ readFilename(ser, _startingSequenceName);
+ ser.syncAsUint16LE(_randomPlayerCursorAllowed);
+
+ uint16 sequenceCount = 0, hotspotCount = 0;
+ ser.syncAsUint16LE(sequenceCount);
+ ser.syncAsUint16LE(hotspotCount);
+
+ _sequences.resize(sequenceCount);
+ for (uint i = 0; i < sequenceCount; ++i) {
+ readRandomSequence(ser, _sequences[i]);
+ }
+
+ _videoDescs.resize(hotspotCount);
+ for (uint i = 0; i < hotspotCount; ++i) {
+ _videoDescs[i].readData(stream);
+ }
+
+ // "RandomMovie" picks any sequence; otherwise look up by name.
+ // Only the first sequence is played; chained playback is TODO.
+ if (!_sequences.empty()) {
+ int startIdx = -1;
+ if (_startingSequenceName == "RandomMovie") {
+ startIdx = g_nancy->_randomSource->getRandomNumber(_sequences.size() - 1);
+ } else {
+ for (uint i = 0; i < _sequences.size(); ++i) {
+ if (_sequences[i].name.toString() == _startingSequenceName) {
+ startIdx = (int)i;
+ break;
+ }
+ }
+ if (startIdx < 0) {
+ warning("PlayRandomMovie: starting sequence \"%s\" is not part of this AR",
+ _startingSequenceName.c_str());
+ }
+ }
+
+ if (startIdx >= 0) {
+ const RandomSequence &src = _sequences[startIdx];
+ _videoName = src.name;
+ _firstFrame = src.startFrame;
+ _videoType = kVideoPlaytypeBink;
+ _videoFormat = kLargeVideoFormat;
+ _videoSceneChange = kMovieNoSceneChange;
+ _playerCursorAllowed = (byte)_randomPlayerCursorAllowed;
+ _playDirection = kPlayMovieForward;
+ }
+ }
+
+ _sound.name = "NO SOUND";
+}
+
void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
Common::Serializer ser(&stream, nullptr);
ser.setVersion(g_nancy->getGameType());
+ if (_isRandom) {
+ readRandomMovieData(ser, stream);
+ return;
+ }
+
readFilename(ser, _videoName);
readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
readFilename(ser, _bitmapOverlayName, kGameTypeVampire, kGameTypeNancy9);
@@ -213,6 +289,10 @@ void PlaySecondaryMovie::execute() {
if (activeFrame != -1) {
_screenPosition = _videoDescs[activeFrame].destRect;
setVisible(true);
+ } else if (_isRandom && _videoDescs.empty()) {
+ // No hotspots: play full-viewport instead of gating on a frame match.
+ _screenPosition = NancySceneState.getViewport().getBounds();
+ setVisible(true);
} else {
setVisible(false);
}
@@ -293,5 +373,25 @@ void PlaySecondaryMovie::execute() {
}
}
+// --- PlayRandomMovieControl --------------------------------------------
+
+void PlayRandomMovieControl::readData(Common::SeekableReadStream &stream) {
+ _targetA = stream.readUint32LE();
+ _targetB = stream.readUint32LE();
+ _targetC = stream.readUint32LE();
+ _flagsOrIndex = stream.readUint16LE();
+ _trailing = stream.readByte();
+}
+
+void PlayRandomMovieControl::execute() {
+ // Original binary debug string:
+ // "STOP AT_PLAY_RANDOM_MOVIE AR %d in Scene %d"
+ // so two of the chunk fields identify the target AR / scene.
+ // Until the exact identification scheme is known, this stub just
+ // warns and completes.
+ warning("PlayRandomMovieControl is not yet fully implemented");
+ finishExecution();
+}
+
} // End of namespace Action
} // End of namespace Nancy
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 555706712b8..b68e47502d0 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -42,7 +42,10 @@ class InteractiveVideo;
// - hiding of player cursor (and thus, disabling input);
// - setting event flags on a specific frame, as well as at the end of the video;
// - changing the scene after playback ends
-// Mostly used for cinematics, with some occasional uses for background animations
+// Mostly used for cinematics, with some occasional uses for background animations.
+//
+// Construct with `isRandom = true` for Nancy 11's AT_PLAY_RANDOM_MOVIE (AR 45):
+// the chunk holds a list of sequences and one is picked at readData() time.
class PlaySecondaryMovie : public RenderActionRecord {
public:
static const byte kMovieSceneChange = 5;
@@ -59,7 +62,24 @@ public:
FlagDescription flagDesc;
};
- PlaySecondaryMovie() : RenderActionRecord(8) {}
+ // Name of the next sequence to chain to once the current one finishes.
+ struct NextSequenceRef {
+ Common::Path name;
+ uint16 unknown = 0;
+ };
+
+ // `name` is both the sequence id and the movie filename.
+ struct RandomSequence {
+ Common::Path name;
+ uint16 startFrame = 0;
+ uint16 unknown_0x23 = 0;
+ int32 minPauseMs = 0;
+ int32 maxPauseMs = 0;
+ uint16 unknown_0x2D = 0;
+ Common::Array<NextSequenceRef> nextSequences;
+ };
+
+ PlaySecondaryMovie(bool isRandom = false) : RenderActionRecord(8), _isRandom(isRandom) {}
virtual ~PlaySecondaryMovie();
void init() override;
@@ -92,16 +112,56 @@ public:
Common::ScopedPtr<Video::VideoDecoder> _decoder;
+ // Random-movie state (only populated when _isRandom).
+ bool _isRandom = false;
+ // "RandomMovie" picks any sequence; otherwise it names the starting one.
+ Common::String _startingSequenceName;
+ uint16 _randomPlayerCursorAllowed = kPlayerCursorAllowed;
+ Common::Array<RandomSequence> _sequences;
+
bool isViewportRelative() const override { return true; }
protected:
- Common::String getRecordTypeName() const override { return "PlaySecondaryMovie"; }
+ Common::String getRecordTypeName() const override {
+ return _isRandom ? "PlayRandomMovie" : "PlaySecondaryMovie";
+ }
+
+ // `ser` and `stream` must wrap the same input; `stream` is only
+ // needed for SecondaryVideoDescription::readData.
+ void readRandomMovieData(Common::Serializer &ser, Common::SeekableReadStream &stream);
+ void readRandomSequence(Common::Serializer &ser, RandomSequence &seq);
Graphics::ManagedSurface _fullFrame;
int _curViewportFrame = -1;
bool _isFinished = false;
};
+// Companion AR for the random-movie variant of PlaySecondaryMovie. When
+// executed it locates the matching PlaySecondaryMovie(isRandom=true)
+// instance (by sequence/AR name) and stops it. First seen in Nancy 11
+// (AR 46, AT_PLAY_RANDOM_MOVIE_CONTROL).
+//
+// Chunk layout: fixed 15 bytes (3 Ã uint32 + uint16 + uint8). The exact
+// meaning of each field is not yet reverse-engineered; the runtime debug
+// string is "STOP AT_PLAY_RANDOM_MOVIE AR %d in Scene %d", so at least two
+// of these fields identify the target AR.
+class PlayRandomMovieControl : public ActionRecord {
+public:
+ PlayRandomMovieControl() {}
+
+ void readData(Common::SeekableReadStream &stream) override;
+ void execute() override;
+
+protected:
+ Common::String getRecordTypeName() const override { return "PlayRandomMovieControl"; }
+
+ uint32 _targetA = 0;
+ uint32 _targetB = 0;
+ uint32 _targetC = 0;
+ uint16 _flagsOrIndex = 0;
+ byte _trailing = 0;
+};
+
} // End of namespace Action
} // End of namespace Nancy
Commit: 946eaa3c1e9e3a9d580e7970e214fecb0462ba1a
https://github.com/scummvm/scummvm/commit/946eaa3c1e9e3a9d580e7970e214fecb0462ba1a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-17T04:06:34+03:00
Commit Message:
NANCY: Handle alt surfaces in onebuildpuzzle
Fixes key drawing in the door puzzle in Nancy 10
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 c10539ecda2..222b6b64c86 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.cpp
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
@@ -60,6 +60,16 @@ void OneBuildPuzzle::init() {
}
}
+ p.useAltSurface = false;
+ if (!p.altSrcRect.isEmpty() && !p.isPreRotated) {
+ int aw = p.altSrcRect.width();
+ int ah = p.altSrcRect.height();
+ p.altSurface.create(aw, ah, _image.format);
+ p.altSurface.setTransparentColor(_drawSurface.getTransparentColor());
+ p.altSurface.blitFrom(_image, p.altSrcRect, Common::Point(0, 0));
+ p.useAltSurface = true;
+ }
+
// Initial position and rotation
if (p.isPreRotated) {
// Pre-rotated pieces start at their slot and are already placed
@@ -127,12 +137,15 @@ void OneBuildPuzzle::readData(Common::SeekableReadStream &stream) {
Piece &p = _pieces[i];
if (isNancy10) {
- // Fall back to the alt rect when the primary one is empty.
+ // Two rects: altSrc = at-home art, srcRect = active art
Common::Rect altSrc;
readRect(stream, altSrc);
readRect(stream, p.srcRect);
- if (p.srcRect.isEmpty())
+ if (p.srcRect.isEmpty()) {
p.srcRect = altSrc;
+ } else if (!altSrc.isEmpty()) {
+ p.altSrcRect = altSrc;
+ }
} else {
readRect(stream, p.srcRect);
}
@@ -350,6 +363,12 @@ void OneBuildPuzzle::handleInput(NancyInput &input) {
}
}
+ // Re-arm at-home art when the piece lands back on homeRect
+ if (!piece.altSurface.empty() && !piece.placed &&
+ piece.gameRect == piece.homeRect) {
+ piece.useAltSurface = true;
+ }
+
updatePieceRender(_pickedUpPiece);
_isDragging = false;
_pickedUpPiece = -1;
@@ -389,10 +408,12 @@ void OneBuildPuzzle::handleInput(NancyInput &input) {
if ((leftClick || rightClick) && topmostUnplaced != -1) {
_pickedUpPiece = topmostUnplaced;
+ Piece &pp = _pieces[_pickedUpPiece];
+ pp.useAltSurface = false;
+
if (rightClick)
rotatePiece(_pickedUpPiece);
- Piece &pp = _pieces[_pickedUpPiece];
_isDragging = true;
_pickedUpWidth = pp.rotateSurfaces[pp.curRotation].w;
_pickedUpHeight = pp.rotateSurfaces[pp.curRotation].h;
@@ -418,12 +439,16 @@ void OneBuildPuzzle::handleInput(NancyInput &input) {
void OneBuildPuzzle::updatePieceRender(int pieceIdx) {
Piece &p = _pieces[pieceIdx];
- int rot = p.curRotation;
- if (!p.hasSurface[rot])
- rot = 0;
- if (!p.hasSurface[rot])
- return;
- p._drawSurface.create(p.rotateSurfaces[rot], p.rotateSurfaces[rot].getBounds());
+ if (p.useAltSurface && !p.altSurface.empty()) {
+ p._drawSurface.create(p.altSurface, p.altSurface.getBounds());
+ } else {
+ int rot = p.curRotation;
+ if (!p.hasSurface[rot])
+ rot = 0;
+ if (!p.hasSurface[rot])
+ return;
+ p._drawSurface.create(p.rotateSurfaces[rot], p.rotateSurfaces[rot].getBounds());
+ }
p.setTransparent(true);
p.moveTo(p.gameRect);
}
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.h b/engines/nancy/action/puzzle/onebuildpuzzle.h
index 81f2d6f7c49..99acf6dafcc 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.h
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.h
@@ -54,21 +54,25 @@ protected:
Piece() : RenderObject(0) {}
// File data
- Common::Rect srcRect; // Source rect in source image
- Common::Rect slotRect; // Correct placement rect (viewport coords)
- Common::Rect homeRect; // Starting position (viewport coords)
- uint8 defaultRotation = 0; // Rotation index that fits the slot
- bool isPreRotated = false; // Piece starts already in place (slotRect position)
+ Common::Rect srcRect;
+ Common::Rect altSrcRect; // At-home art, shown until first pickup
+ Common::Rect slotRect;
+ Common::Rect homeRect;
+ uint8 defaultRotation = 0;
+ bool isPreRotated = false;
// Runtime
Common::Rect gameRect; // Current viewport-space rect
int curRotation = 0;
bool placed = false;
- // Up to 4 rotation surfaces (rotation 1-3 only exist if canRotateAll or isPreRotated)
+ // Rotations 1-3 only built when canRotateAll or isPreRotated
Graphics::ManagedSurface rotateSurfaces[4];
bool hasSurface[4] = {};
+ Graphics::ManagedSurface altSurface;
+ bool useAltSurface = false;
+
void setZ(uint16 z) { _z = z; _needsRedraw = true; }
bool isViewportRelative() const override { return true; }
More information about the Scummvm-git-logs
mailing list