[Scummvm-git-logs] scummvm master -> 6cfb9c87554f5b8f38b0e19f1e27d24de4c1c72f
bluegr
noreply at scummvm.org
Wed Jul 8 02:36:35 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
88f96d750d NANCY: NANCY11: Cleanup comments
3181ebddc1 NANCY: Add scene change info to action record printing
a7089d53c1 NANCY: Use the kNoScene enum instead of the literal value
031b043a46 NANCY: NANCY11: More work on the keypad terse variant of OrderingPuzzle
db9f3edc3f NANCY: Cleanup comments
6cfb9c8755 NANCY: NANCY12: Implement SewingMachinePuzzle
Commit: 88f96d750d8ae021fa6980a58705dd4ec4b24483
https://github.com/scummvm/scummvm/commit/88f96d750d8ae021fa6980a58705dd4ec4b24483
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:18+03:00
Commit Message:
NANCY: NANCY11: Cleanup comments
Changed paths:
engines/nancy/action/puzzle/cardgamepuzzle.cpp
diff --git a/engines/nancy/action/puzzle/cardgamepuzzle.cpp b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
index 62d34aa2a91..6dfe6888446 100644
--- a/engines/nancy/action/puzzle/cardgamepuzzle.cpp
+++ b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
@@ -501,8 +501,7 @@ void CardGamePuzzle::playVoice(const Common::String &name) {
showSubtitle(name);
}
-// The card-game lines carry no inline caption; the original looks the subtitle up by sound name in
-// the Autotext table. Mirror that and push it to the textbox (as QuizPuzzle does).
+// The card-game lines carry no inline caption; look the subtitle up by sound name in the Autotext table.
void CardGamePuzzle::showSubtitle(const Common::String &soundName) {
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
if (!autotext) {
Commit: 3181ebddc1a0e6e3d6101d7e56f5cba6512b41f2
https://github.com/scummvm/scummvm/commit/3181ebddc1a0e6e3d6101d7e56f5cba6512b41f2
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:20+03:00
Commit Message:
NANCY: Add scene change info to action record printing
Helps debug scenes with complex logic, that may jump to several
different scenes
Changed paths:
engines/nancy/action/actionrecord.h
engines/nancy/action/navigationrecords.h
engines/nancy/action/secondarymovie.h
engines/nancy/action/secondaryvideo.h
engines/nancy/action/soundrecords.h
engines/nancy/console.cpp
diff --git a/engines/nancy/action/actionrecord.h b/engines/nancy/action/actionrecord.h
index a4d6fcf503a..ca9bdaf0390 100644
--- a/engines/nancy/action/actionrecord.h
+++ b/engines/nancy/action/actionrecord.h
@@ -120,6 +120,7 @@ public:
// Used for debugging
virtual Common::String getRecordTypeName() const = 0;
+ virtual Common::String getRecordExtraInfo() const { return ""; }
// Used for handling kCursorType dependency
virtual bool canHaveHotspot() const { return false; }
diff --git a/engines/nancy/action/navigationrecords.h b/engines/nancy/action/navigationrecords.h
index 4d224fe2a55..4c3999c1743 100644
--- a/engines/nancy/action/navigationrecords.h
+++ b/engines/nancy/action/navigationrecords.h
@@ -35,6 +35,8 @@ public:
SceneChangeDescription _sceneChange;
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
+
protected:
Common::String getRecordTypeName() const override { return "SceneChange"; }
};
@@ -187,6 +189,8 @@ public:
SceneChangeDescription _defaultScene;
Common::Array<HotspotDescription> _hotspots;
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Default scene %d", _defaultScene.sceneID); }
+
protected:
Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneCursorTypeSceneChange"; }
};
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index e272dee2722..f70dfcb1a88 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -140,6 +140,8 @@ public:
return _isRandom && !_isDone && !_randomStopRequested;
}
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
+
protected:
Common::String getRecordTypeName() const override {
return _isRandom ? "PlayRandomMovie" : "PlaySecondaryMovie";
diff --git a/engines/nancy/action/secondaryvideo.h b/engines/nancy/action/secondaryvideo.h
index 3167c2677ee..8137060baaf 100644
--- a/engines/nancy/action/secondaryvideo.h
+++ b/engines/nancy/action/secondaryvideo.h
@@ -87,6 +87,8 @@ public:
return g_nancy->getGameType() >= kGameTypeNancy10 ? CursorManager::kHotspotTalk : CursorManager::kHotspot;
}
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
+
protected:
Common::String getRecordTypeName() const override { return "PlaySecondaryVideo"; }
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
index 6679648a775..26e7e79500d 100644
--- a/engines/nancy/action/soundrecords.h
+++ b/engines/nancy/action/soundrecords.h
@@ -110,6 +110,8 @@ public:
SceneChangeDescription _sceneChange;
FlagDescription _flag;
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
+
protected:
Common::String getRecordTypeName() const override;
};
@@ -173,7 +175,8 @@ public:
Common::Array<HotspotDescription> _hotspots; // 0x31
bool canHaveHotspot() const override { return true; }
-
+
+ Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
protected:
Common::String getRecordTypeName() const override { return "PlaySoundMultiHS"; }
};
diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index b7d5068ae2e..b70cad20c37 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -476,6 +476,10 @@ void NancyConsole::printActionRecord(const Action::ActionRecord *record, bool no
record->getRecordTypeName().c_str(),
record->_execType == Action::ActionRecord::kRepeating ? "kRepeating" : "kOneShot");
+ Common::String extraInfo = record->getRecordExtraInfo();
+ if (!extraInfo.empty())
+ debugPrintf("\n\textraInfo: %s", extraInfo.c_str());
+
if (!noDependencies && record->_dependencies.children.size()) {
debugPrintf("\n\tDependencies:");
Commit: a7089d53c16a287cb7127ec27c9002b54efe1c3f
https://github.com/scummvm/scummvm/commit/a7089d53c16a287cb7127ec27c9002b54efe1c3f
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:22+03:00
Commit Message:
NANCY: Use the kNoScene enum instead of the literal value
Changed paths:
engines/nancy/action/puzzle/cardgamepuzzle.cpp
engines/nancy/action/puzzle/twodialpuzzle.cpp
engines/nancy/action/puzzle/typingquizpuzzle.cpp
diff --git a/engines/nancy/action/puzzle/cardgamepuzzle.cpp b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
index 6dfe6888446..f634c710cab 100644
--- a/engines/nancy/action/puzzle/cardgamepuzzle.cpp
+++ b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
@@ -590,11 +590,11 @@ void CardGamePuzzle::execute() {
const bool tie = (playerScore == aiScore);
// The result normally uses the first outcome scene; the second is the tie/alternate
- // path the 0xff deal mode takes. 9999 marks an absent scene.
- if (tie && _dealMode == 0xff && _winSceneStartEnemy != 9999) {
+ // path the 0xff deal mode takes.
+ if (tie && _dealMode == 0xff && _winSceneStartEnemy != kNoScene) {
sceneChange.sceneID = _winSceneStartEnemy;
} else {
- sceneChange.sceneID = (_winSceneStartPlayer != 9999) ? _winSceneStartPlayer : _winSceneStartEnemy;
+ sceneChange.sceneID = (_winSceneStartPlayer != kNoScene) ? _winSceneStartPlayer : _winSceneStartEnemy;
}
// Record the outcome: the player-win flag when ahead, else the alternate flag on a tie.
diff --git a/engines/nancy/action/puzzle/twodialpuzzle.cpp b/engines/nancy/action/puzzle/twodialpuzzle.cpp
index e1021b188f2..79b8668ee0b 100644
--- a/engines/nancy/action/puzzle/twodialpuzzle.cpp
+++ b/engines/nancy/action/puzzle/twodialpuzzle.cpp
@@ -143,7 +143,7 @@ void TwoDialPuzzle::execute() {
// The matched solution supplies the scene to change to.
for (uint i = 0; i < _solutions.size(); ++i) {
const DialSolution &sol = _solutions[i];
- if (sol.sceneID != 9999 &&
+ if (sol.sceneID != kNoScene &&
_currentPositions[0] == sol.positions[0] &&
_currentPositions[1] == sol.positions[1] &&
NancySceneState.getEventFlag(sol.condition.label, sol.condition.flag)) {
diff --git a/engines/nancy/action/puzzle/typingquizpuzzle.cpp b/engines/nancy/action/puzzle/typingquizpuzzle.cpp
index 24a6c5f2d4c..a825d1722ab 100644
--- a/engines/nancy/action/puzzle/typingquizpuzzle.cpp
+++ b/engines/nancy/action/puzzle/typingquizpuzzle.cpp
@@ -380,7 +380,7 @@ void TypingQuizPuzzle::redraw() {
void TypingQuizPuzzle::triggerSceneChange() {
if (_reachedTarget) {
- if (_winScene.sceneID != 9999)
+ if (_winScene.sceneID != kNoScene)
NancySceneState.changeScene(_winScene);
if (_winFlag != -1)
NancySceneState.setEventFlag(_winFlag, g_nancy->_true);
Commit: 031b043a465972330c94dead433aaf908f1a2737
https://github.com/scummvm/scummvm/commit/031b043a465972330c94dead433aaf908f1a2737
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:23+03:00
Commit Message:
NANCY: NANCY11: More work on the keypad terse variant of OrderingPuzzle
Fixes the name check puzzle before the actual card game puzzle against
the Betty automaton.
A small hack has been added for this puzzle, which modifies the solve
scene override and allows the player to get the needed token given when
winning this puzzle.
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 8e6fca0a82c..21eea76a44f 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -305,18 +305,27 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
}
} 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();
- // The Nancy 11 multi-stage terse keypad chains scenes: each scene holds one stage and
- // the grid is preceded by the scene to advance to once this stage's code is entered.
- // Standalone/final keypads omit it. A grid column count can never exceed maxNumElements,
- // so a larger leading value is that scene id - it overrides the solve scene's target
- // (the rest of the solve scene change is reused), then the real column count follows.
- if (g_nancy->getGameType() >= kGameTypeNancy11 && columns > maxNumElements) {
- _solveExitScene._sceneChange.sceneID = columns;
- columns = stream.readUint16LE();
+ // In Nancy 11 the grid block is preceded by the scene to advance to on solving, which
+ // overrides the solve scene's target (the rest of the solve scene change is reused).
+ // 0 and 9999 mean "none", falling back to the solve scene read earlier.
+ if (g_nancy->getGameType() >= kGameTypeNancy11) {
+ uint16 advanceSceneID = stream.readUint16LE();
+
+ // HACK: In Nancy11, in the Betty automaton scene, this is set to scene 2721, but
+ // scene 2720 (the one set initially) is the one that eventually allows the player
+ // to gain the needed token to proceed.
+ // TODO: What is the correct way to handle this?
+ if (g_nancy->getGameType() == kGameTypeNancy11 && advanceSceneID == 2721 &&
+ _solveExitScene._sceneChange.sceneID == 2720)
+ advanceSceneID = 2720;
+
+ if (advanceSceneID != 0 && advanceSceneID != kNoScene) {
+ _solveExitScene._sceneChange.sceneID = advanceSceneID;
+ }
}
+ uint16 columns = stream.readUint16LE();
uint16 rows = stream.readUint16LE();
uint16 width = stream.readUint16LE();
@@ -486,7 +495,10 @@ void OrderingPuzzle::execute() {
}
}
- if (_clickedSequence.size() > maxNumPressed) {
+ // Puzzles with a manual check button (e.g. the Nancy 11 Betty automaton keypad)
+ // keep the entered buttons down until the check button is pressed, so they
+ // must not auto-clear the entry here based on its length.
+ if (!_needButtonToCheckSuccess && _clickedSequence.size() > maxNumPressed) {
clearAllElements();
return;
}
@@ -542,19 +554,41 @@ void OrderingPuzzle::execute() {
}
if ((_puzzleType == kKeypad || _puzzleType == kKeypadTerse) && _needButtonToCheckSuccess) {
- // KeypadPuzzle and KeypadTersePuzzle move to the "success" scene regardless whether the
- // puzzle was solved or not, provided the check button is pressed. (e.g. the Nancy 11 Betty
- // automaton keypad, where a red button confirms the entry.)
- if (_checkButtonPressed) {
- if (!g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
- if (solved) {
- NancySceneState.setEventFlag(_solveExitScene._flag);
+ // The entry is only acted on when the confirm ("red") button is pressed.
+ if (!_checkButtonPressed) {
+ return;
+ }
+
+ if (g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
+ // Wait for the button-press sound to finish first.
+ return;
+ }
+
+ if (g_nancy->getGameType() >= kGameTypeNancy11) {
+ // The confirm button does nothing on an empty entry. A pad that carries a code also
+ // does nothing on a non-matching entry (wrong names or a wrong order), clearing it.
+ // Otherwise it advances.
+ bool codedMismatch = !_correctSequence.empty() && !solved && _itemsStayDown;
+
+ if (_clickedSequence.empty() || codedMismatch) {
+ if (codedMismatch) {
+ clearAllElements();
}
- } else {
+
+ _checkButtonPressed = false;
+ Common::Rect destRect = _checkButtonDest;
+ destRect.translate(-_screenPosition.left, -_screenPosition.top);
+ _drawSurface.fillRect(destRect, _drawSurface.getTransparentColor());
+ _needsRedraw = true;
return;
}
+
+ NancySceneState.setEventFlag(_solveExitScene._flag);
} else {
- return;
+ // Earlier games advance to the success scene regardless; the flag is set only on a solve.
+ if (solved) {
+ NancySceneState.setEventFlag(_solveExitScene._flag);
+ }
}
} else {
if (solved) {
Commit: db9f3edc3ff280436961d941fe1b35b2328af56d
https://github.com/scummvm/scummvm/commit/db9f3edc3ff280436961d941fe1b35b2328af56d
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:24+03:00
Commit Message:
NANCY: Cleanup comments
Changed paths:
engines/nancy/action/arfactory.cpp
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 31f0e60d385..41046c6bcc1 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -181,15 +181,13 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new HotMultiframeMultiSceneCursorTypeSceneChange(); // Moved from 24 to 27 in Nancy10
case 28:
return new InteractiveVideo(); // Moved from 26 to 28 in Nancy10
- case 29:
- // Nancy 10+
+ case 29: // Nancy10
return new ControlUIItems();
case 30: // Nancy11
return new StopPlayerScrolling();
case 31: // Nancy11
return new StartPlayerScrolling();
- case 32:
- // Nancy 10+
+ case 32: // Nancy10
return new UIPopupPrepScene();
case 45: // Nancy11 - random-movie variant of PlaySecondaryMovie
return new PlaySecondaryMovie(true);
@@ -272,7 +270,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new ModifyListEntry(ModifyListEntry::kDelete);
case 73:
return new ModifyListEntry(ModifyListEntry::kMark);
- case 74: // Nancy 10
+ case 74: // Nancy10
return new FrameTextBox(true);
case 75:
if (g_nancy->getGameType() <= kGameTypeNancy9)
@@ -367,20 +365,15 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new PopInvViewPriorScene();
case 126:
return new GoInvViewScene();
- case 128:
- // Nancy10+
+ case 128: // Nancy10
return new CellPhonePopCellSceneFromStack();
- case 129:
- // Nancy10+
+ case 129: // Nancy10
return new SetCellPhoneBatteryAndSignal();
- case 130:
- // Nancy10+
+ case 130: // Nancy10
return new ChangeCellPhoneInfo();
- case 131:
- // Nancy10+
+ case 131: // Nancy10
return new AddSearchLink();
- case 132:
- // Nancy12+
+ case 132: // Nancy12
return new ResourceUse();
case 140:
if (g_nancy->getGameType() >= kGameTypeNancy12)
Commit: 6cfb9c87554f5b8f38b0e19f1e27d24de4c1c72f
https://github.com/scummvm/scummvm/commit/6cfb9c87554f5b8f38b0e19f1e27d24de4c1c72f
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-08T05:36:25+03:00
Commit Message:
NANCY: NANCY12: Implement SewingMachinePuzzle
In this puzzle, the player feeds a piece of cloth through a sewing
machine needle
Changed paths:
A engines/nancy/action/puzzle/sewingmachinepuzzle.cpp
A engines/nancy/action/puzzle/sewingmachinepuzzle.h
engines/nancy/action/arfactory.cpp
engines/nancy/module.mk
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 41046c6bcc1..785b0b2b59b 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -69,6 +69,7 @@
#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
#include "engines/nancy/action/puzzle/safedialpuzzle.h"
#include "engines/nancy/action/puzzle/setplayerclock.h"
+#include "engines/nancy/action/puzzle/sewingmachinepuzzle.h"
#include "engines/nancy/action/puzzle/sliderpuzzle.h"
#include "engines/nancy/action/puzzle/sortpuzzle.h"
#include "engines/nancy/action/puzzle/soundequalizerpuzzle.h"
@@ -456,8 +457,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new PlaySoundEventFlagTerse();
// -- Nancy 12 new puzzles/action records --
case 162:
- // TODO: Nancy12 - sewing machine puzzle.
- return nullptr;
+ return new SewingMachinePuzzle();
case 163:
return new MirrorLightPuzzle();
case 164:
diff --git a/engines/nancy/action/puzzle/sewingmachinepuzzle.cpp b/engines/nancy/action/puzzle/sewingmachinepuzzle.cpp
new file mode 100644
index 00000000000..659fd93badd
--- /dev/null
+++ b/engines/nancy/action/puzzle/sewingmachinepuzzle.cpp
@@ -0,0 +1,214 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/random.h"
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/action/puzzle/sewingmachinepuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+void SewingMachinePuzzle::readData(Common::SeekableReadStream &stream) {
+ // 87-byte PuzzleBase header blob.
+ readFilename(stream, _imageName); // blob 0x00
+ readRect(stream, _rects[0]); // blob 0x21
+ readRect(stream, _rects[1]); // blob 0x31
+ _directionVector.x = stream.readSint32LE(); // blob 0x41
+ _directionVector.y = stream.readSint32LE(); // blob 0x45
+ _extentVector.x = stream.readSint32LE(); // blob 0x49
+ _extentVector.y = stream.readSint32LE(); // blob 0x4d
+ for (int i = 0; i < 3; ++i) {
+ _params[i] = stream.readSint16LE(); // blob 0x51 / 0x53 / 0x55
+ }
+
+ _soundBlock.readData(stream);
+
+ readActionZoneArray(stream, _zones);
+}
+
+void SewingMachinePuzzle::classifyZones() {
+ for (uint i = 0; i < _zones.size(); ++i) {
+ const ActionZone &z = _zones[i];
+ switch (z.type) {
+ case 0x0b: // the needle line: touching it plays a stitch cue and sets a flag
+ _collisionZone = i;
+ break;
+ case 0x0c: // bottom trigger: its SpecialEffect carries the win scene + fade
+ _triggerZones.push_back(i);
+ if (z.specialEffectId >= 1000 && _winScene.sceneID == kNoScene) {
+ _winScene.sceneID = z.specialEffectId;
+ _winEventFlag = z.val49;
+ if (z.hasSpecialEffect) {
+ _winHasFade = true;
+ _winFadeType = z.seType;
+ _winFadeTotalTime = z.seTotalTime;
+ _winFadeToBlackTime = z.seFadeToBlackTime;
+ _winFadeRect = z.seRect;
+ }
+ }
+ break;
+ case 0x14: // play-area boundary
+ _boundaryZone = i;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void SewingMachinePuzzle::playSoundBlock(const RandomSoundBlock &block) {
+ if (block.names.empty()) {
+ return;
+ }
+
+ uint idx = block.names.size() == 1 ? 0 : g_nancy->_randomSource->getRandomNumber(block.names.size() - 1);
+ const Common::String &name = block.names[idx];
+ if (name.empty() || name == "NO SOUND") {
+ return;
+ }
+
+ SoundDescription desc;
+ desc.name = name;
+ desc.channelID = block.channel;
+ desc.numLoops = block.numLoops > 0 ? block.numLoops : 1;
+ desc.volume = block.volume;
+
+ g_nancy->_sound->loadSound(desc);
+ g_nancy->_sound->playSound(desc);
+}
+
+void SewingMachinePuzzle::drawCloth() {
+ // The cloth is a tall strip; the fixed needle (drawn by the scene) sits over it.
+ // The scene shows through wherever the strip does not cover.
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+ _drawSurface.blitFrom(_image, _clothPos);
+ _needsRedraw = true;
+}
+
+void SewingMachinePuzzle::feedCloth(const Common::Point &delta) {
+ Common::Point newPos(CLIP<int>(_clothPos.x + delta.x, -_maxSteer, _maxSteer),
+ CLIP<int>(_clothPos.y + delta.y, -_maxFeed, 0));
+
+ int moved = ABS(newPos.x - _clothPos.x) + ABS(newPos.y - _clothPos.y);
+ _clothPos = newPos;
+
+ if (moved == 0) {
+ // A pause resets the stitch stroke, matching the original's hysteresis.
+ _strokeDistance = 0.0;
+ return;
+ }
+
+ _strokeDistance += moved;
+ drawCloth();
+
+ // A stitch cue (needle sound) fires each time the cloth advances past the threshold.
+ if (_strokeDistance >= _params[1] && _collisionZone >= 0) {
+ _strokeDistance = 0.0;
+ playSoundBlock(_zones[_collisionZone]._sound);
+ }
+
+ // The seam is finished once the cloth has been fed all the way through.
+ if (_maxFeed > 0 && _clothPos.y <= -_maxFeed && !_solved) {
+ _solved = true;
+ _state = kActionTrigger;
+ }
+}
+
+void SewingMachinePuzzle::init() {
+ Common::Rect vpBounds = NancySceneState.getViewport().getBounds();
+ _drawSurface.create(vpBounds.width(), vpBounds.height(),
+ g_nancy->_graphics->getInputPixelFormat());
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+ setTransparent(true);
+ setVisible(true);
+ moveTo(vpBounds);
+
+ g_nancy->_resource->loadImage(_imageName, _image);
+ _image.setTransparentColor(_drawSurface.getTransparentColor());
+
+ // Feed scrolls the whole strip past the viewport; steer lets the cloth wiggle
+ // horizontally to follow the seam under the needle (range is a tunable guess).
+ _maxFeed = MAX(0, _image.h - _drawSurface.h);
+ _maxSteer = 100;
+}
+
+void SewingMachinePuzzle::execute() {
+ switch (_state) {
+ case kBegin:
+ init();
+ registerGraphics();
+ classifyZones();
+ playSoundBlock(_soundBlock); // start the sewing-machine ambience
+ drawCloth();
+ _state = kRun;
+ break;
+ case kRun:
+ break;
+ case kActionTrigger:
+ // Seam finished: raise the win flag, then cross-dissolve to the scene the
+ // trigger zone's SpecialEffect points at.
+ if (_winEventFlag != -1) {
+ NancySceneState.setEventFlag(_winEventFlag, g_nancy->_true);
+ }
+ if (_winScene.sceneID >= 1000 && _winScene.sceneID != kNoScene) {
+ if (_winHasFade) {
+ NancySceneState.specialEffect(_winFadeType, _winFadeTotalTime, _winFadeToBlackTime, _winFadeRect);
+ }
+ NancySceneState.changeScene(_winScene);
+ }
+ finishExecution();
+ break;
+ }
+}
+
+void SewingMachinePuzzle::handleInput(NancyInput &input) {
+ if (_state != kRun) {
+ return;
+ }
+
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if (input.input & NancyInput::kLeftMouseButtonDown) {
+ _dragging = true;
+ _lastDragPos = input.mousePos;
+ }
+
+ if (_dragging && (input.input & NancyInput::kLeftMouseButtonHeld)) {
+ feedCloth(input.mousePos - _lastDragPos);
+ _lastDragPos = input.mousePos;
+ }
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _dragging = false;
+ _strokeDistance = 0.0;
+ }
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/sewingmachinepuzzle.h b/engines/nancy/action/puzzle/sewingmachinepuzzle.h
new file mode 100644
index 00000000000..3e82d498087
--- /dev/null
+++ b/engines/nancy/action/puzzle/sewingmachinepuzzle.h
@@ -0,0 +1,111 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_SEWINGMACHINEPUZZLE_H
+#define NANCY_ACTION_SEWINGMACHINEPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+#include "engines/nancy/commontypes.h"
+#include "engines/nancy/action/navigationrecords.h"
+#include "engines/nancy/action/actionzone.h"
+
+namespace Nancy {
+namespace Action {
+
+// Nancy12 sewing-machine puzzle (action record 162): the player feeds a piece of
+// cloth through a sewing machine needle.
+//
+// The cloth is a tall strip that the player drags to feed under the fixed needle;
+// stitch/needle sounds play as it slides, and feeding it all the way through
+// cross-dissolves to the trigger zone's win scene.
+// TODO: the horizontal steer range and the drag-to-feed scale are tunable guesses.
+class SewingMachinePuzzle : public RenderActionRecord {
+public:
+ SewingMachinePuzzle() : RenderActionRecord(7) {}
+ virtual ~SewingMachinePuzzle() {}
+
+ void init() override;
+
+ void readData(Common::SeekableReadStream &stream) override;
+ void execute() override;
+ void handleInput(NancyInput &input) override;
+
+ bool isViewportRelative() const override { return true; }
+
+protected:
+ Common::String getRecordTypeName() const override { return "SewingMachinePuzzle"; }
+
+ // Sorts the ActionZone array into its gameplay roles.
+ void classifyZones();
+ // Plays one entry of a random-sound block (needle/stitch cues).
+ void playSoundBlock(const RandomSoundBlock &block);
+ // Draws the sewing view (the "BED_Sewing_OVL" overlay).
+ void drawCloth();
+ // Advances the sewing by a drag delta, firing stitch/needle cues and marking
+ // the puzzle solved once enough cloth has been fed through.
+ void feedCloth(const Common::Point &delta);
+
+ // Background image ("BED_Sewing_OVL").
+ Common::Path _imageName;
+
+ // 87-byte PuzzleBase header blob: image name + two cloth regions + a direction
+ // vector + an extent vector (its magnitude = the seam length) + three params
+ // (_params[1] = stitch distance threshold, _params[2] = its hysteresis).
+ Common::Rect _rects[2];
+ Common::Point _directionVector;
+ Common::Point _extentVector;
+ int16 _params[3] = {};
+
+ RandomSoundBlock _soundBlock;
+ Common::Array<ActionZone> _zones;
+
+ // ActionZone roles (indices into _zones; -1 == none).
+ int _collisionZone = -1; // type 0x0b: the needle line + its sounds/flag
+ int _boundaryZone = -1; // type 0x14
+ Common::Array<uint> _triggerZones; // type 0x0c: completion triggers
+
+ // Win transition from a trigger zone's SpecialEffect: its leading id is the win
+ // scene, the effect is the fade played over the change.
+ SceneChangeDescription _winScene;
+ int16 _winEventFlag = -1;
+ bool _winHasFade = false;
+ byte _winFadeType = 0;
+ uint16 _winFadeTotalTime = 0;
+ uint16 _winFadeToBlackTime = 0;
+ Common::Rect _winFadeRect;
+
+ // Runtime state. The cloth is a tall strip drawn at (_clothPos); dragging scrolls
+ // it under the fixed needle (feed = vertical, steer = horizontal).
+ Common::Point _clothPos; // top-left of the cloth on screen
+ int _maxFeed = 0; // vertical scroll range (image height - viewport)
+ int _maxSteer = 0; // horizontal steer range each way
+ double _strokeDistance = 0.0; // movement accumulated toward the next stitch
+ bool _dragging = false;
+ Common::Point _lastDragPos;
+ bool _solved = false;
+
+ Graphics::ManagedSurface _image;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_SEWINGMACHINEPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index fc4410e58af..451fc52218d 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -53,6 +53,7 @@ MODULE_OBJS = \
action/puzzle/rotatinglockpuzzle.o \
action/puzzle/safedialpuzzle.o \
action/puzzle/setplayerclock.o \
+ action/puzzle/sewingmachinepuzzle.o \
action/puzzle/sliderpuzzle.o \
action/puzzle/sortpuzzle.o \
action/puzzle/soundequalizerpuzzle.o \
More information about the Scummvm-git-logs
mailing list