[Scummvm-git-logs] scummvm master -> ce70c890a6d8016a97d49a9795614ddbfc3336ac
bluegr
noreply at scummvm.org
Sun Jul 19 02:38:15 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:
1a38c9a894 NANCY: NANCY10: Always use the grab-hand cursor for GridMapPuzzle
f674c058a4 NANCY: NANCY13: Handle talkable characters with random animations
9ccfefbf06 NANCY: NANCY13: Initial work on the changes to cellphone (e.g. camera)
ce70c890a6 NANCY: NANCY13: Initial ActionZone changes needed for PachinkoPuzzle
Commit: 1a38c9a8948a8f81ee6aa914ef9bb30698b41799
https://github.com/scummvm/scummvm/commit/1a38c9a8948a8f81ee6aa914ef9bb30698b41799
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:27:55+03:00
Commit Message:
NANCY: NANCY10: Always use the grab-hand cursor for GridMapPuzzle
Fix #16980
Changed paths:
engines/nancy/action/puzzle/gridmappuzzle.cpp
diff --git a/engines/nancy/action/puzzle/gridmappuzzle.cpp b/engines/nancy/action/puzzle/gridmappuzzle.cpp
index bb1659be529..ec59d18b315 100644
--- a/engines/nancy/action/puzzle/gridmappuzzle.cpp
+++ b/engines/nancy/action/puzzle/gridmappuzzle.cpp
@@ -347,20 +347,20 @@ void GridMapPuzzle::handleInput(NancyInput &input) {
if (!hitMap)
hitItems = hitTestItems(mouseVP, iRow, iCol);
+ // The whole puzzle uses the grab-hand cursor; only the exit hotspot differs.
+ // Picking something up doesn't change it.
if (!hitMap && !hitItems) {
if (!_exitHotspot.isEmpty() && _exitHotspot.contains(mouseVP)) {
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
if (input.input & NancyInput::kLeftMouseButtonUp)
_subState = kExitToCancel;
} else {
- g_nancy->_cursor->setCursorType(_heldItem != -1 ? CursorManager::kDragHand
- : CursorManager::kNormal);
+ g_nancy->_cursor->setCursorType(CursorManager::kDropHand);
}
return;
}
- g_nancy->_cursor->setCursorType(_heldItem != -1 ? CursorManager::kDragHand
- : CursorManager::kNormal);
+ g_nancy->_cursor->setCursorType(CursorManager::kDropHand);
if (!(input.input & NancyInput::kLeftMouseButtonUp))
return;
Commit: f674c058a42d6d6e2df6730b499d7508ba25e731
https://github.com/scummvm/scummvm/commit/f674c058a42d6d6e2df6730b499d7508ba25e731
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:31:43+03:00
Commit Message:
NANCY: NANCY13: Handle talkable characters with random animations
Changed paths:
engines/nancy/action/secondarymovie.cpp
engines/nancy/action/secondarymovie.h
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 99279aa5947..bbf06540bab 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -19,7 +19,9 @@
*
*/
+#include "engines/nancy/cursor.h"
#include "engines/nancy/graphics.h"
+#include "engines/nancy/input.h"
#include "engines/nancy/nancy.h"
#include "engines/nancy/sound.h"
#include "engines/nancy/util.h"
@@ -63,6 +65,32 @@ bool PlaySecondaryMovie::isPersistentAcrossScenes() const {
return _isRandom && g_nancy->getGameType() < kGameTypeNancy13 && !_isDone && !_randomStopRequested;
}
+void PlaySecondaryMovie::handleInput(NancyInput &input) {
+ // The character's box (set as the hotspot while it is on screen) is
+ // clickable; clicking opens its conversation scene, and hovering drives the
+ // recognition movie. The talk hover cursor is applied by ActionManager via
+ // getHoverCursor().
+ if (!_hasHotspot || _talkSceneID == kNoScene) {
+ _isHovered = false;
+ return;
+ }
+
+ _isHovered = NancySceneState.getViewport().convertViewportToScreen(_hotspot).contains(input.mousePos);
+
+ if (_isHovered && (input.input & NancyInput::kLeftMouseButtonUp)) {
+ input.eatMouseInput();
+ SceneChangeDescription desc;
+ desc.sceneID = _talkSceneID;
+ NancySceneState.changeScene(desc);
+ }
+}
+
+CursorManager::CursorType PlaySecondaryMovie::getHoverCursor() const {
+ // The character's own cursor type (a raw Nancy13 cursor id) comes from the
+ // secondary record; cursorSetFromScript() routes it through the raw-slot path.
+ return (CursorManager::CursorType)_talkCursorType;
+}
+
void PlaySecondaryMovie::readRandomSequence(Common::Serializer &ser, RandomSequence &seq) {
readFilename(ser, seq.name);
ser.syncAsUint16LE(seq.startFrame);
@@ -92,14 +120,17 @@ void PlaySecondaryMovie::readRandomSequence(Common::Serializer &ser, RandomSeque
void PlaySecondaryMovie::readSecondaryRandomMovie(Common::Serializer &ser, RandomSequence &seq) {
// Nancy13's random-movie chunk carries one extra "secondary" movie record
- // (e.g. a character's recognition animation) between the sequence list and
- // the hotspot list. It is a name + 5 uint16 + a next-list; only the first
- // two uint16s (start/last frame) have known roles so far, but the whole
- // record must be consumed here or the hotspot list below misaligns.
+ // (a character's recognition animation) between the sequence list and the
+ // hotspot list: a name + 5 uint16 + a next-list. The 4th uint16 is the scene
+ // to open when the character is clicked (its conversation); 9999 means the
+ // character isn't clickable. The whole record must be consumed here or the
+ // hotspot list below misaligns.
readFilename(ser, seq.name);
ser.syncAsUint16LE(seq.startFrame);
ser.syncAsUint16LE(seq.lastFrame);
- ser.skip(6); // three uint16 fields, roles not yet characterized
+ ser.syncAsUint16LE(_talkCursorType); // hover cursor for the character
+ ser.syncAsUint16LE(_talkSceneID);
+ ser.skip(2); // conversation frameID (0 in known data)
uint16 nextCount = 0;
ser.syncAsUint16LE(nextCount);
@@ -201,6 +232,23 @@ bool PlaySecondaryMovie::activateRandomSequence(int index) {
return true;
}
+bool PlaySecondaryMovie::activateSecondaryMovie() {
+ _videoName = _secondaryMovie.name;
+ _firstFrame = _secondaryMovie.startFrame;
+ _lastFrame = _secondaryMovie.lastFrame;
+
+ if (!_decoder.loadFile(_videoName)) {
+ warning("PlayRandomMovie: couldn't load recognition movie %s", _videoName.toString().c_str());
+ return false;
+ }
+
+ resolveSentinelFrames();
+
+ _isFinished = false;
+ _curViewportFrame = -1;
+ return true;
+}
+
void PlaySecondaryMovie::resolveSentinelFrames() {
// Random sequences use -1/-2 for the start/last frame to mean "play the
// movie's own first/last frame". Resolve them now that the decoder (and
@@ -526,6 +574,19 @@ void PlaySecondaryMovie::execute() {
break;
}
+ // Talkable character: swap immediately between the idle loop and the
+ // recognition ("turn around") movie as the mouse enters/leaves the
+ // character, without waiting for the current cycle to finish.
+ if (isTalkable()) {
+ if (_isHovered && !_playingSecondary) {
+ _playingSecondary = true;
+ activateSecondaryMovie();
+ } else if (!_isHovered && _playingSecondary) {
+ _playingSecondary = false;
+ activateRandomSequence(_activeSequenceIndex);
+ }
+ }
+
int newFrame = NancySceneState.getSceneInfo().frameID;
if (newFrame != _curViewportFrame) {
@@ -541,13 +602,22 @@ void PlaySecondaryMovie::execute() {
if (activeFrame != -1) {
_screenPosition = _videoDescs[activeFrame].destRect;
setVisible(true);
+
+ // Nancy13 talkable characters: the character's on-screen box
+ // doubles as a clickable hotspot that opens its conversation.
+ if (_talkSceneID != kNoScene) {
+ _hotspot = _screenPosition;
+ _hasHotspot = true;
+ }
} else if (_isRandom) {
// Random movies aren't gated on hotspot/viewport-frame
// matches the way regular PSMs are: play full viewport.
_screenPosition = NancySceneState.getViewport().getBounds();
setVisible(true);
+ _hasHotspot = false;
} else {
setVisible(false);
+ _hasHotspot = false;
}
}
@@ -608,6 +678,16 @@ void PlaySecondaryMovie::execute() {
// by a PlayRandomMovieControl, wind the AR down normally.
if (_randomStopRequested) {
_state = kActionTrigger;
+ } else if (isTalkable()) {
+ // Hover swaps are handled at the top of kRun. Here we only
+ // keep the idle loop going; the recognition movie, once
+ // finished, holds on its last frame while the mouse stays.
+ if (!_playingSecondary) {
+ // Replay the idle movie in place without reopening it.
+ _isFinished = false;
+ _decoder.seekToFrame(_playDirection == kPlayMovieReverse ? _lastFrame : _firstFrame);
+ _decoder.pauseVideo(false);
+ }
} else {
int picked = rollNextSequence();
if (picked >= 0) {
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 4a1d659f0ca..3073c61f35a 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -121,11 +121,17 @@ public:
uint16 _randomPlayerCursorAllowed = kPlayerCursorAllowed;
Common::Array<RandomSequence> _sequences;
- // Nancy13+ carries one extra "secondary" movie (e.g. a recognition
- // animation) after the sequence list. Stored for future playback; reading
- // it is required so the trailing hotspot list stays aligned.
+ // Nancy13+ carries one extra "secondary" movie (a recognition animation)
+ // after the sequence list. Stored for future playback; reading it is
+ // required so the trailing hotspot list stays aligned.
RandomSequence _secondaryMovie;
+ // Nancy13 talkable characters: the scene to open when the character is
+ // clicked (its conversation). kNoScene means the character isn't clickable.
+ uint16 _talkSceneID = kNoScene;
+ // Hover cursor for the character (a raw Nancy13 cursor id from the chunk).
+ uint16 _talkCursorType = 0;
+
// Chain state. After a sequence's movie finishes the engine rolls a
// weighted pick: "stay" -> enter pause for a random duration and
// re-roll; valid next-sequence -> swap to that sequence's movie.
@@ -135,6 +141,11 @@ public:
uint32 _randomPauseEndTime = 0;
bool _randomStopRequested = false;
+ // Talkable-character hover state: whether the mouse is over the character,
+ // and whether the recognition (secondary) movie is currently playing.
+ bool _isHovered = false;
+ bool _playingSecondary = false;
+
// Called by PlayRandomMovieControl::execute() to wind down the AR.
void stopRandom() { _randomStopRequested = true; }
@@ -145,6 +156,13 @@ public:
bool isPersistentAcrossScenes() const override;
+ // Nancy13 talkable characters expose the character's on-screen box as a
+ // clickable hotspot with a talk cursor; clicking opens _talkSceneID, and
+ // hovering plays the recognition ("turn around") movie.
+ void handleInput(NancyInput &input) override;
+ CursorManager::CursorType getHoverCursor() const override;
+ bool cursorSetFromScript() const override { return _isRandom && _talkSceneID != kNoScene; }
+
Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
protected:
@@ -164,6 +182,13 @@ protected:
// and reload the decoder. Returns true on success.
bool activateRandomSequence(int index);
+ // Load & start the recognition (secondary) movie in place of the idle loop.
+ bool activateSecondaryMovie();
+
+ // A Nancy13 talkable character: has a conversation scene and a recognition
+ // movie to swap to on hover.
+ bool isTalkable() const { return _isRandom && _talkSceneID != kNoScene && !_secondaryMovie.name.empty(); }
+
// Pick the next sequence (or "stay") per the weighted random rules.
// Returns -1 if "stay" was picked (and sets up the pause state),
// or the chosen sequence index otherwise.
Commit: 9ccfefbf06532bdf3c3e6552e615895868e88afd
https://github.com/scummvm/scummvm/commit/9ccfefbf06532bdf3c3e6552e615895868e88afd
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:34:27+03:00
Commit Message:
NANCY: NANCY13: Initial work on the changes to cellphone (e.g. camera)
The cellphone has been revamped in Nancy13 (e.g. it has a camera now).
Add handling for these fields, to keep the rest of the phone functional
Changed paths:
engines/nancy/enginedata.cpp
engines/nancy/enginedata.h
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index da61b27a392..bbcbfb30482 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -964,64 +964,131 @@ UICL::UICL(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
dialPadSlots[i].soundName = nameBuf;
}
- // Screen-frame and label rects
- readRect(*chunkStream, dialHilite.srcRect);
- readRect(*chunkStream, dialHilite.destRect);
- readRect(*chunkStream, screenOutSrcRect);
- statusTextX = chunkStream->readSint32LE();
- statusTextY = chunkStream->readSint32LE();
- readRect(*chunkStream, welcomeScreen.srcRect);
- readRect(*chunkStream, welcomeScreen.destRect);
-
char labelBuf[21];
+ const bool isNancy13 = g_nancy->getGameType() >= kGameTypeNancy13;
+
+ // Version-specific preamble: Nancy 13 replaced the dial-highlight /
+ // screen-out / welcome block with a camera sub-UI, and moved the
+ // dial/web/dir labels ahead of the status labels.
+ if (isNancy13) {
+ readRect(*chunkStream, cameraViewSrcRect);
+ cameraTextX = chunkStream->readSint32LE();
+ cameraTextY = chunkStream->readSint32LE();
+ readFilename(*chunkStream, cameraViewImageName);
+ readFilename(*chunkStream, cameraClickSound);
+ readRect(*chunkStream, pictureDisplayRect);
+
+ // Camera picture-slot / thumbnail data (int16 rects). Not yet mapped.
+ chunkStream->skip(114);
+
+ readRect(*chunkStream, noPictureScreenRect);
+
+ // The dialed-number baseline is the camera text position (Nancy 13
+ // dropped the separate statusText field). welcomeScreen is read from the
+ // screen-graphic block further down.
+ statusTextX = cameraTextX;
+ statusTextY = cameraTextY;
+
+ // Eight dial/web/dir/camera label SrcDestRectPairs across three on-screen
+ // columns (dest x = 437 / 492 / 551), each with alternate glyph variants;
+ // take one pair per column for the dial / web / dir labels.
+ readRect(*chunkStream, dialLabel.srcRect); // column 1 (x=437)
+ readRect(*chunkStream, dialLabel.destRect);
+ chunkStream->skip(32); // second x=437 variant
+ readRect(*chunkStream, webLabel.srcRect); // column 2 (x=492)
+ readRect(*chunkStream, webLabel.destRect);
+ readRect(*chunkStream, dirLabel.srcRect); // column 3 (x=551)
+ readRect(*chunkStream, dirLabel.destRect);
+ chunkStream->skip(4 * 32); // remaining variants
+ } else {
+ readRect(*chunkStream, dialHilite.srcRect);
+ readRect(*chunkStream, dialHilite.destRect);
+ readRect(*chunkStream, screenOutSrcRect);
+ statusTextX = chunkStream->readSint32LE();
+ statusTextY = chunkStream->readSint32LE();
+ readRect(*chunkStream, welcomeScreen.srcRect);
+ readRect(*chunkStream, welcomeScreen.destRect);
+ }
+
for (uint i = 0; i < kNumStatusLabels; ++i) {
chunkStream->read(labelBuf, 20);
labelBuf[20] = '\0';
statusLabels[i] = labelBuf;
}
- readRect(*chunkStream, dialLabel.srcRect);
- readRect(*chunkStream, dialLabel.destRect);
- readRect(*chunkStream, webLabel.srcRect);
- readRect(*chunkStream, webLabel.destRect);
- readRect(*chunkStream, dirLabel.srcRect);
- readRect(*chunkStream, dirLabel.destRect);
+ if (!isNancy13) {
+ // Nancy 13 reads the dial/web/dir labels in the camera block above.
+ readRect(*chunkStream, dialLabel.srcRect);
+ readRect(*chunkStream, dialLabel.destRect);
+ readRect(*chunkStream, webLabel.srcRect);
+ readRect(*chunkStream, webLabel.destRect);
+ readRect(*chunkStream, dirLabel.srcRect);
+ readRect(*chunkStream, dirLabel.destRect);
+ }
- // Help "?" button widget (3 rects).
- readRect(*chunkStream, helpButton.srcRectIdle);
- readRect(*chunkStream, helpButton.srcRectPressed);
- readRect(*chunkStream, helpButton.destRect);
+ // Help "?" button (3 rects) + its CVTX text key. Nancy 13 orders the key
+ // before the button and adds a second key.
+ if (isNancy13) {
+ readFilename(*chunkStream, helpTextKey);
+ readRect(*chunkStream, helpButton.srcRectIdle);
+ readRect(*chunkStream, helpButton.srcRectPressed);
+ readRect(*chunkStream, helpButton.destRect);
+ readFilename(*chunkStream, helpTextKey2);
+ } else {
+ readRect(*chunkStream, helpButton.srcRectIdle);
+ readRect(*chunkStream, helpButton.srcRectPressed);
+ readRect(*chunkStream, helpButton.destRect);
+ readFilename(*chunkStream, helpTextKey);
+ }
- // Screen-content sprite block
- readFilename(*chunkStream, helpTextKey);
readRect(*chunkStream, signalSpriteSrc);
readRect(*chunkStream, signalSpriteSrcAlt);
readRect(*chunkStream, signalSpriteDest);
readRect(*chunkStream, batterySpriteSrc);
readRect(*chunkStream, batterySpriteSrcAlt);
readRect(*chunkStream, batterySpriteDest);
- readRect(*chunkStream, typeMessage.srcRect);
- readRect(*chunkStream, typeMessage.destRect);
- readRect(*chunkStream, connectedLabel.srcRect);
- readRect(*chunkStream, connectedLabel.destRect);
- readRect(*chunkStream, connectingSpriteSrc);
- readRect(*chunkStream, connectingSpriteSrcAlt);
- readRect(*chunkStream, connectingSpriteDest);
-
- if (g_nancy->getGameType() >= kGameTypeNancy11) {
- // TODO: Looks to be a new coordinate - values (548, 50)
- chunkStream->skip(4);
- chunkStream->skip(4);
+
+ if (isNancy13) {
+ // Welcome / idle screen graphic: a normal source variant, a no-signal
+ // source variant, then the on-screen dest (all 171x164). This is what
+ // drawWelcomeScreen blits, and it carries the top-row button
+ // backgrounds; its dest rect also bounds the small-LCD directory list.
+ // Nancy 13 dropped the separate typeMessage / connectedLabel /
+ // connectingSprite fields.
+ readRect(*chunkStream, welcomeScreen.srcRect);
+ chunkStream->skip(16); // no-signal source variant
+ readRect(*chunkStream, welcomeScreen.destRect);
+ chunkStream->skip(8); // trailing pad
+ } else {
+ readRect(*chunkStream, typeMessage.srcRect);
+ readRect(*chunkStream, typeMessage.destRect);
+ readRect(*chunkStream, connectedLabel.srcRect);
+ readRect(*chunkStream, connectedLabel.destRect);
+ readRect(*chunkStream, connectingSpriteSrc);
+ readRect(*chunkStream, connectingSpriteSrcAlt);
+ readRect(*chunkStream, connectingSpriteDest);
+
+ if (g_nancy->getGameType() >= kGameTypeNancy11) {
+ // TODO: Looks to be a new coordinate - values (548, 50)
+ chunkStream->skip(8);
+ }
+
+ readRect(*chunkStream, onlineHeading.srcRect);
+ readRect(*chunkStream, onlineHeading.destRect);
}
- readRect(*chunkStream, onlineHeading.srcRect);
- readRect(*chunkStream, onlineHeading.destRect);
readRect(*chunkStream, fullEmptyScreenSrc);
readRect(*chunkStream, emailListContainer);
readRect(*chunkStream, dirArrowSrc);
readRect(*chunkStream, dirCursorSrc);
- readRect(*chunkStream, dirHeading.srcRect);
- readRect(*chunkStream, dirHeading.destRect);
+
+ if (isNancy13) {
+ // Online / directory headings â N13 changed these values, not mapped yet.
+ chunkStream->skip(48);
+ } else {
+ readRect(*chunkStream, dirHeading.srcRect);
+ readRect(*chunkStream, dirHeading.destRect);
+ }
for (uint i = 0; i < kNumSubButtons; ++i) {
readRect(*chunkStream, subButtons[i].srcRectIdle);
@@ -1029,15 +1096,19 @@ UICL::UICL(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
readRect(*chunkStream, subButtons[i].destRect);
}
- // Heading/icon rect pairs
readRect(*chunkStream, searchHeading.srcRect);
readRect(*chunkStream, searchHeading.destRect);
readRect(*chunkStream, emailIconUnread);
readRect(*chunkStream, emailIconSelected);
readRect(*chunkStream, emailHeading.srcRect);
readRect(*chunkStream, emailHeading.destRect);
- readRect(*chunkStream, helpHeading.srcRect);
- readRect(*chunkStream, helpHeading.destRect);
+
+ if (!isNancy13) {
+ // Nancy 13 has no separate help heading in this block.
+ readRect(*chunkStream, helpHeading.srcRect);
+ readRect(*chunkStream, helpHeading.destRect);
+ }
+
readRect(*chunkStream, browserHeading.srcRect);
readRect(*chunkStream, browserHeading.destRect);
@@ -1053,6 +1124,11 @@ UICL::UICL(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
initialSearch.flag = chunkStream->readSint16LE();
initialSearch.eventFlag = chunkStream->readSint16LE();
+ if (isNancy13) {
+ // Three RGB colors for the phone screen, added in Nancy 13.
+ chunkStream->read(screenColors, sizeof(screenColors));
+ }
+
fontId1 = chunkStream->readUint16LE();
fontId2 = chunkStream->readUint16LE();
@@ -1077,6 +1153,18 @@ UICL::UICL(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
chunkStream->read(c.unknownSuffix, sizeof(c.unknownSuffix));
}
+
+ if (isNancy13) {
+ // Trailing captured-picture slot table, added in Nancy 13.
+ const uint16 pictureCount = chunkStream->readUint16LE();
+ pictures.resize(pictureCount);
+ for (uint i = 0; i < pictureCount; ++i) {
+ PictureRecord &p = pictures[i];
+ p.id = chunkStream->readUint16LE();
+ readRect(*chunkStream, p.rect);
+ chunkStream->read(p.unknown, sizeof(p.unknown));
+ }
+ }
}
UICO::UICO(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 9355488d99c..f3edb1fca3f 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -689,6 +689,26 @@ struct UICL : public EngineData {
uint16 contactCount = 0;
Common::Array<Contact> contacts;
+
+ // Nancy 13 added a camera / pictures sub-UI to the cell phone, which
+ // reorganized the chunk body. The fields below are only populated for
+ // Nancy 13 and later.
+ struct PictureRecord {
+ uint16 id = 0;
+ Common::Rect rect;
+ byte unknown[6] = {};
+ };
+
+ Common::Rect cameraViewSrcRect; // camera viewfinder SRC on the overlay
+ int32 cameraTextX = 0;
+ int32 cameraTextY = 0;
+ Common::Path cameraViewImageName; // "UI_CellCamView_OVL"
+ Common::Path cameraClickSound;
+ Common::Rect pictureDisplayRect; // where a captured picture is shown
+ Common::Rect noPictureScreenRect; // "no pictures" placeholder
+ Common::Path helpTextKey2; // second CVTX key (phone-use help)
+ byte screenColors[9] = {}; // 3 RGB colors for the phone screen
+ Common::Array<PictureRecord> pictures; // captured-picture slots (up to 50)
};
// New conversation popup UI (the text strip that appears above the taskbar
Commit: ce70c890a6d8016a97d49a9795614ddbfc3336ac
https://github.com/scummvm/scummvm/commit/ce70c890a6d8016a97d49a9795614ddbfc3336ac
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:36:23+03:00
Commit Message:
NANCY: NANCY13: Initial ActionZone changes needed for PachinkoPuzzle
Changed paths:
engines/nancy/action/actionzone.cpp
engines/nancy/action/actionzone.h
diff --git a/engines/nancy/action/actionzone.cpp b/engines/nancy/action/actionzone.cpp
index 3764d711d38..c7eb6066ccd 100644
--- a/engines/nancy/action/actionzone.cpp
+++ b/engines/nancy/action/actionzone.cpp
@@ -27,7 +27,7 @@
namespace Nancy {
namespace Action {
-void ActionZone::readData(Common::SeekableReadStream &stream) {
+void ActionZone::readData(Common::SeekableReadStream &stream, bool isNancy13) {
// Base ActionZone (matches the original "Action Zone Boundary OVL" reader).
typeField = stream.readSint32LE();
type = typeField & 0xFF;
@@ -42,11 +42,21 @@ void ActionZone::readData(Common::SeekableReadStream &stream) {
val49 = stream.readSint16LE();
val4b = stream.readByte();
+ // The Nancy13 base carries an extra int32 here (before the sound block).
+ if (isNancy13) {
+ stream.skip(4);
+ }
+
// Random-sound block: count, then that many 33-byte names + 8 bytes of params.
_sound.readData(stream);
- // Subtype-specific trailing data. Fields not yet needed are skipped to keep
- // the stream aligned.
+ readSubtype(stream, isNancy13);
+}
+
+// Subtype-specific trailing data. Fields not yet needed are skipped to keep the stream
+// aligned. The Nancy12 and Nancy13 layouts are identical apart from three subtypes
+// (0x0d / 0x15 / 0x16), which branch on isNancy13.
+void ActionZone::readSubtype(Common::SeekableReadStream &stream, bool isNancy13) {
switch (type) {
case 1: // base only
case 5:
@@ -72,7 +82,7 @@ void ActionZone::readData(Common::SeekableReadStream &stream) {
case 3:
stream.skip(8); // double
break;
- case 0x17: // Flat Tire (min/max)
+ case 0x17: // Flat Tire (min/max) - Nancy12 only
stream.skip(8); // int32 + int32
break;
case 4:
@@ -86,19 +96,30 @@ void ActionZone::readData(Common::SeekableReadStream &stream) {
tailId = stream.readSint16LE();
tailFlag = stream.readByte();
break;
- case 0x15: // special effect + a trailing int32 (purpose unconfirmed)
- readSpecialEffect(stream);
- stream.skip(4);
+ case 0x15:
+ // Nancy12: special effect + a trailing int32. Nancy13: the flat-tire zone
+ // (min/max int32), with no special effect.
+ if (isNancy13) {
+ stream.skip(8);
+ } else {
+ readSpecialEffect(stream);
+ stream.skip(4);
+ }
break;
- case 0x0d: // OverlayZone
- readOverlayZone(stream);
+ case 0x0d: // OverlayZone (Nancy13 carries one extra int32)
+ readOverlayZone(stream, isNancy13);
break;
- case 0x16: // OverlayZone + int32
- readOverlayZone(stream);
- stream.skip(4);
+ case 0x16:
+ // Nancy12: OverlayZone + int32. Nancy13: a short bumper record (two bytes + int16).
+ if (isNancy13) {
+ stream.skip(4);
+ } else {
+ readOverlayZone(stream, false);
+ stream.skip(4);
+ }
break;
default:
- warning("Nancy12 ActionZone: unknown type %d - chunk may desync", type);
+ warning("ActionZone: unknown type %d - chunk may desync", type);
break;
}
}
@@ -121,7 +142,7 @@ void ActionZone::readSpecialEffect(Common::SeekableReadStream &stream) {
readRect(stream, seRect);
}
-void ActionZone::readOverlayZone(Common::SeekableReadStream &stream) {
+void ActionZone::readOverlayZone(Common::SeekableReadStream &stream, bool isNancy13) {
readFilename(stream, overlayName);
int16 numSrcRects = stream.readSint16LE();
@@ -133,12 +154,15 @@ void ActionZone::readOverlayZone(Common::SeekableReadStream &stream) {
}
readRect(stream, overlayDestRect);
+ if (isNancy13) {
+ stream.skip(4); // extra int32 vs Nancy12
+ }
stream.skip(4); // int32
stream.skip(1); // byte (loop/play mode)
stream.skip(4); // int32
}
-void readActionZoneArray(Common::SeekableReadStream &stream, Common::Array<ActionZone> &out) {
+void readActionZoneArray(Common::SeekableReadStream &stream, Common::Array<ActionZone> &out, bool isNancy13) {
int16 count = stream.readSint16LE();
if (count <= 0) {
return;
@@ -146,7 +170,7 @@ void readActionZoneArray(Common::SeekableReadStream &stream, Common::Array<Actio
out.resize(count);
for (int i = 0; i < count; ++i) {
- out[i].readData(stream);
+ out[i].readData(stream, isNancy13);
}
}
diff --git a/engines/nancy/action/actionzone.h b/engines/nancy/action/actionzone.h
index 75e32642548..2397ca3b12a 100644
--- a/engines/nancy/action/actionzone.h
+++ b/engines/nancy/action/actionzone.h
@@ -76,15 +76,19 @@ struct ActionZone {
Common::Array<Common::Rect> overlaySrcRects;
Common::Rect overlayDestRect;
- void readData(Common::SeekableReadStream &stream);
+ // The Nancy13 pinball layout (AR 175) differs from the Nancy12 one: the base carries an
+ // extra int32 before the sound block, and subtypes 0x0d/0x15/0x16 have different trailers.
+ // Pass isNancy13 = true to parse it; the default keeps the Nancy12 behaviour.
+ void readData(Common::SeekableReadStream &stream, bool isNancy13 = false);
private:
void readSpecialEffect(Common::SeekableReadStream &stream);
- void readOverlayZone(Common::SeekableReadStream &stream);
+ void readOverlayZone(Common::SeekableReadStream &stream, bool isNancy13);
+ void readSubtype(Common::SeekableReadStream &stream, bool isNancy13);
};
// Reads an int16 count, then that many ActionZones.
-void readActionZoneArray(Common::SeekableReadStream &stream, Common::Array<ActionZone> &out);
+void readActionZoneArray(Common::SeekableReadStream &stream, Common::Array<ActionZone> &out, bool isNancy13 = false);
} // End of namespace Action
} // End of namespace Nancy
More information about the Scummvm-git-logs
mailing list