[Scummvm-git-logs] scummvm master -> a26e5cfb3e82cfd3f6be068d9601a4822a08e0e5

bluegr noreply at scummvm.org
Sat Jul 18 00:19:52 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
a26e5cfb3e NANCY: NANCY13: Implement WordFindPuzzle


Commit: a26e5cfb3e82cfd3f6be068d9601a4822a08e0e5
    https://github.com/scummvm/scummvm/commit/a26e5cfb3e82cfd3f6be068d9601a4822a08e0e5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-18T03:12:43+03:00

Commit Message:
NANCY: NANCY13: Implement WordFindPuzzle

Word-find puzzle. Despite the name it is not a free grid drag-select:
the player works one target word at a time (progressing across scene
visits), building a connected chain of orthogonally-adjacent letter
cells; once the chain has the word's length and matches the answer
path, the word's animation plays and the next word becomes active.

Changed paths:
  A engines/nancy/action/puzzle/wordfindpuzzle.cpp
  A engines/nancy/action/puzzle/wordfindpuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/module.mk
    engines/nancy/puzzledata.cpp
    engines/nancy/puzzledata.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 86fa7b7dc3c..386be86607a 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -86,6 +86,7 @@
 #include "engines/nancy/action/puzzle/twodialpuzzle.h"
 #include "engines/nancy/action/puzzle/typingquizpuzzle.h"
 #include "engines/nancy/action/puzzle/whalesurvivorpuzzle.h"
+#include "engines/nancy/action/puzzle/wordfindpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
@@ -194,6 +195,11 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 		return new StartPlayerScrolling();
 	case 32:	// Nancy10
 		return new UIPopupPrepScene();
+	case 40:
+		if (g_nancy->getGameType() <= kGameTypeNancy1)
+			return new LightningOn(); // Only used in TVD
+		else
+			return new SpecialEffect();
 	case 41:	// Nancy14
 	case 44:	// Nancy14 (adds a trailing volume byte)
 		return new PlaySecondaryMovie();
@@ -207,11 +213,6 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 	case 47:	// Nancy14 - PlaySecondaryMovie subclass with a per-frame flag list
 		// TODO: not yet implemented
 		return nullptr;
-	case 40:
-		if (g_nancy->getGameType() <= kGameTypeNancy1)
-			return new LightningOn();	// Only used in TVD
-		else
-			return new SpecialEffect();
 	case 50:
 		return new ConversationVideo(); // PlayPrimaryVideoChan0
 	case 51:
@@ -471,12 +472,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 	case 169:
 		return new StepObjectsPuzzle();
 	case 170:
-		if (g_nancy->getGameType() >= kGameTypeNancy13) {
-			// WordFindPuzzle, new in Nancy13. This reuses the slot that used
-			// to hold SetPlayerClock (which itself moved to 140 in Nancy12).
-			// TODO: not yet implemented
-			return nullptr;
-		}
+		if (g_nancy->getGameType() >= kGameTypeNancy13)
+			return new WordFindPuzzle();
 		return new SetPlayerClock();	// moved to 140 in Nancy12
 	case 171:
 		return new TurningPuzzle();	// moved from 209 in Nancy13
diff --git a/engines/nancy/action/puzzle/wordfindpuzzle.cpp b/engines/nancy/action/puzzle/wordfindpuzzle.cpp
new file mode 100644
index 00000000000..fd9c0eebfa7
--- /dev/null
+++ b/engines/nancy/action/puzzle/wordfindpuzzle.cpp
@@ -0,0 +1,404 @@
+/* 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 "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/cursor.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/puzzledata.h"
+
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/action/puzzle/wordfindpuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+WordFindPuzzleData *WordFindPuzzle::getPuzzleData() const {
+	return (WordFindPuzzleData *)NancySceneState.getPuzzleData(WordFindPuzzleData::getTag());
+}
+
+void WordFindPuzzle::readData(Common::SeekableReadStream &stream) {
+	// 82-byte base header.
+	_cursorType = stream.readUint16LE();	// 0x00
+	stream.skip(4);							// 0x02
+	_cellGapX = stream.readUint16LE();		// 0x06
+	_cellGapY = stream.readUint16LE();		// 0x08
+	stream.skip(0x20);						// 0x0a
+	_solveScene.sceneID = stream.readUint16LE();	// 0x2a - shown once every word is found
+	stream.skip(3);							// 0x2c
+	readFilename(stream, _solutionImageName);	// 0x2f
+	uint16 numWords = stream.readUint16LE();	// 0x50
+
+	_words.resize(numWords);
+	for (uint i = 0; i < numWords; ++i) {
+		Word &w = _words[i];
+		readFilename(stream, w.gridImageName);
+		readFilename(stream, w.overlayImageName);
+		readFilename(stream, w.animName);
+
+		uint16 coordCount = stream.readUint16LE();
+		w.answerCoords.resize(coordCount);
+		for (uint16 j = 0; j < coordCount; ++j) {
+			w.answerCoords[j].x = stream.readSint16LE();
+			w.answerCoords[j].y = stream.readSint16LE();
+		}
+
+		w.eventFlagLabel = stream.readSint16LE();
+		uint16 rectCount = stream.readUint16LE();
+		readRectArray(stream, w.letterRects, rectCount);
+	}
+
+	// The shared 23-byte exit-hotspot record.
+	int16 numZones = stream.readSint16LE();
+	for (int16 i = 0; i < numZones; ++i) {
+		Common::Rect r;
+		readRect(stream, r);
+		uint16 cursorType = stream.readUint16LE();
+		uint16 sceneID = stream.readUint16LE();
+		uint16 frameID = stream.readUint16LE();
+		stream.skip(1);
+
+		if (i == 0) {
+			_exitHotspot = r;
+			_exitCursorType = cursorType;
+			_exitScene.sceneID = sceneID;
+			_exitScene.frameID = (frameID == 0xffff) ? 0 : frameID;
+		}
+	}
+
+	_sounds.resize(4);
+	for (uint i = 0; i < 4; ++i) {
+		_sounds[i].readData(stream);
+	}
+}
+
+Common::Point WordFindPuzzle::gridCoord(const Common::Rect &rect) const {
+	// The grid is anchored at the very first word's first letter (a global origin in the
+	// original), with the pitch being a letter's size plus the header's gap.
+	if (_words.empty() || _words[0].letterRects.empty()) {
+		return Common::Point(0, 0);
+	}
+
+	// The original derives the pitch from the raw (inclusive) rect: cellGap + (right - left).
+	// readRect() has already made our rects exclusive (++right/++bottom), so subtract that
+	// back out, otherwise the pitch is one pixel too large and the integer division lands on
+	// the wrong row/column for every cell past the origin.
+	const Common::Rect &origin = _words[0].letterRects[0];
+	int pitchX = _cellGapX + (origin.right - origin.left - 1);
+	int pitchY = _cellGapY + (origin.bottom - origin.top - 1);
+	if (pitchX <= 0) {
+		pitchX = 1;
+	}
+	if (pitchY <= 0) {
+		pitchY = 1;
+	}
+
+	return Common::Point((rect.left - origin.left) / pitchX, (rect.top - origin.top) / pitchY);
+}
+
+bool WordFindPuzzle::isAdjacent(const Common::Point &a, const Common::Point &b) const {
+	int dx = ABS(a.x - b.x);
+	int dy = ABS(a.y - b.y);
+	return (dx + dy) == 1;
+}
+
+int WordFindPuzzle::letterAtCursor(const Common::Point &mousePos) const {
+	if ((uint)_currentWord >= _words.size()) {
+		return -1;
+	}
+
+	const Common::Array<Common::Rect> &rects = _words[_currentWord].letterRects;
+	for (uint i = 0; i < rects.size(); ++i) {
+		if (NancySceneState.getViewport().convertViewportToScreen(rects[i]).contains(mousePos)) {
+			return (int)i;
+		}
+	}
+	return -1;
+}
+
+int WordFindPuzzle::numSelected() const {
+	int count = 0;
+	for (uint i = 0; i < _selected.size(); ++i) {
+		if (_selected[i]) {
+			++count;
+		}
+	}
+	return count;
+}
+
+bool WordFindPuzzle::chainMatchesAnswer() const {
+	// A word is spelled correctly when every selected letter's grid cell is one of the
+	// word's answer cells (and the counts already match).
+	const Word &w = _words[_currentWord];
+	for (uint i = 0; i < _selected.size(); ++i) {
+		if (!_selected[i]) {
+			continue;
+		}
+
+		Common::Point c = gridCoord(w.letterRects[i]);
+		bool found = false;
+		for (uint j = 0; j < w.answerCoords.size(); ++j) {
+			if (w.answerCoords[j] == c) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found) {
+			return false;
+		}
+	}
+	return true;
+}
+
+void WordFindPuzzle::loadWord() {
+	if ((uint)_currentWord >= _words.size()) {
+		_allFound = true;
+		return;
+	}
+
+	const Word &w = _words[_currentWord];
+	_gridImage.free();
+	_overlayImage.free();
+	g_nancy->_resource->loadImage(w.gridImageName, _gridImage);
+	if (!w.overlayImageName.empty()) {
+		g_nancy->_resource->loadImage(w.overlayImageName, _overlayImage);
+	}
+
+	_selected.clear();
+	_selected.resize(w.letterRects.size(), false);
+}
+
+void WordFindPuzzle::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);
+
+	// The active word carries over from earlier visits to this puzzle.
+	WordFindPuzzleData *data = getPuzzleData();
+	_currentWord = data ? data->currentWord : 0;
+	if (_currentWord < 0) {
+		_currentWord = 0;
+	}
+
+	_allFound = ((uint)_currentWord >= _words.size());
+	if (!_allFound) {
+		loadWord();
+	}
+
+	NancySceneState.setNoHeldItem();
+	redraw();
+	registerGraphics();
+}
+
+void WordFindPuzzle::redraw() {
+	_drawSurface.clear(g_nancy->_graphics->getTransColor());
+
+	if (!_allFound && (uint)_currentWord < _words.size() && !_gridImage.empty()) {
+		_drawSurface.blitFrom(_gridImage, Common::Point(0, 0));
+
+		// Show the overlay art at each selected letter (the overlay image lines up with the
+		// grid, so a selected cell's region is its highlighted version).
+		const Word &w = _words[_currentWord];
+		for (uint i = 0; i < _selected.size(); ++i) {
+			if (_selected[i] && !_overlayImage.empty()) {
+				const Common::Rect &r = w.letterRects[i];
+				if (r.left >= 0 && r.top >= 0 && r.right <= _overlayImage.w && r.bottom <= _overlayImage.h) {
+					_drawSurface.blitFrom(_overlayImage, r, Common::Point(r.left, r.top));
+				}
+			}
+		}
+
+		// While the "found" animation plays it is drawn on top of the grid.
+		if (_playingMovie && _movie.isVideoLoaded()) {
+			_movie.drawFrame(_drawSurface, Common::Point(0, 0));
+		}
+	}
+
+	_needsRedraw = true;
+}
+
+void WordFindPuzzle::startFoundAnimation() {
+	const Word &w = _words[_currentWord];
+	if (!w.animName.empty() && _movie.loadFile(w.animName) && _movie.getFrameCount() > 0) {
+		_movie.playRange(0, _movie.getFrameCount() - 1);
+		_playingMovie = true;
+		redraw();
+	} else {
+		// No animation available: just resolve the word.
+		finishCurrentWord();
+	}
+}
+
+void WordFindPuzzle::finishCurrentWord() {
+	const Word &w = _words[_currentWord];
+	if (w.eventFlagLabel != -1) {
+		NancySceneState.setEventFlag(w.eventFlagLabel, g_nancy->_true);
+	}
+
+	++_currentWord;
+	WordFindPuzzleData *data = getPuzzleData();
+	if (data) {
+		data->currentWord = _currentWord;
+	}
+
+	if ((uint)_currentWord >= _words.size()) {
+		_allFound = true;
+	} else {
+		loadWord();
+	}
+
+	redraw();
+}
+
+void WordFindPuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		_state = kRun;
+		// fall through
+	case kRun: {
+		if (_allFound) {
+			_state = kActionTrigger;
+			break;
+		}
+
+		// While the found-word animation is playing, no input is taken; when it ends the
+		// word's event flag is set and the puzzle advances.
+		if (_playingMovie) {
+			if (_movie.update()) {
+				_movie.drawFrame(_drawSurface, Common::Point(0, 0));
+				_needsRedraw = true;
+			}
+
+			if (!_movie.isRangePlaying()) {
+				_playingMovie = false;
+				_movie.close();
+				finishCurrentWord();
+			}
+
+			break;
+		}
+
+		const Word &w = _words[_currentWord];
+		if (numSelected() == (int)w.answerCoords.size() && !w.answerCoords.empty()) {
+			if (chainMatchesAnswer()) {
+				startFoundAnimation();
+			} else {
+				// A wrong path: clear it so the player can try again.
+				for (uint i = 0; i < _selected.size(); ++i) {
+					_selected[i] = false;
+				}
+				redraw();
+			}
+		}
+
+		break;
+	}
+	case kActionTrigger: {
+		const SceneChangeDescription &sc = _allFound ? _solveScene : _exitScene;
+		if (sc.sceneID != kNoScene) {
+			NancySceneState.changeScene(sc);
+		}
+
+		finishExecution();
+		break;
+	}
+	}
+}
+
+void WordFindPuzzle::handleInput(NancyInput &input) {
+	if (_state != kRun || _allFound || _playingMovie) {
+		return;
+	}
+
+	int letter = letterAtCursor(input.mousePos);
+	if (letter >= 0) {
+		g_nancy->_cursor->setCursorType((CursorManager::CursorType)_cursorType, true);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			const Word &w = _words[_currentWord];
+			Common::Point c = gridCoord(w.letterRects[letter]);
+
+			if (_selected[letter]) {
+				// The clicked letter is always removed; removing one from the middle of the chain
+				// (adjacent to 2+ selected letters) breaks it, so the whole selection is reset.
+				int neighbors = 0;
+				for (uint i = 0; i < _selected.size(); ++i) {
+					if (_selected[i] && i != (uint)letter && isAdjacent(c, gridCoord(w.letterRects[i]))) {
+						++neighbors;
+					}
+				}
+				_selected[letter] = false;
+				if (neighbors >= 2) {
+					for (uint i = 0; i < _selected.size(); ++i) {
+						_selected[i] = false;
+					}
+				}
+			} else {
+				// Extend the chain: valid as the first letter, or next to at least one already-
+				// selected letter (a winding word can touch two of them).
+				int neighbors = 0;
+				for (uint i = 0; i < _selected.size(); ++i) {
+					if (_selected[i] && isAdjacent(c, gridCoord(w.letterRects[i]))) {
+						++neighbors;
+					}
+				}
+				if (numSelected() == 0 || neighbors >= 1) {
+					_selected[letter] = true;
+					if (!_sounds.empty()) {
+						SoundDescription desc;
+						desc.name = _sounds[0].names.empty() ? "" : _sounds[0].names[0];
+						desc.channelID = _sounds[0].channel;
+						desc.numLoops = 1;
+						desc.volume = _sounds[0].volume;
+						if (!desc.name.empty() && desc.name != "NO SOUND") {
+							g_nancy->_sound->loadSound(desc);
+							g_nancy->_sound->playSound(desc);
+						}
+					}
+				}
+			}
+
+			redraw();
+		}
+
+		input.eatMouseInput();
+		return;
+	}
+
+	if (!_exitHotspot.isEmpty() &&
+			NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+		g_nancy->_cursor->setCursorType((CursorManager::CursorType)_exitCursorType, true);
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			_state = kActionTrigger;
+		}
+	}
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/wordfindpuzzle.h b/engines/nancy/action/puzzle/wordfindpuzzle.h
new file mode 100644
index 00000000000..2d8fb9ecd6c
--- /dev/null
+++ b/engines/nancy/action/puzzle/wordfindpuzzle.h
@@ -0,0 +1,108 @@
+/* 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_WORDFINDPUZZLE_H
+#define NANCY_ACTION_WORDFINDPUZZLE_H
+
+#include "engines/nancy/commontypes.h"
+#include "engines/nancy/movieplayer.h"
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+
+struct WordFindPuzzleData;
+
+namespace Action {
+
+// Word-find puzzle, new in Nancy13 (AR 170). Despite the name it is not a free
+// grid drag-select: the player works one target word at a time (progressing across
+// scene visits), building a connected chain of orthogonally-adjacent letter cells;
+// once the chain has the word's length and matches the answer path, the word's
+// animation plays and the next word becomes active.
+class WordFindPuzzle : public RenderActionRecord {
+public:
+	WordFindPuzzle() : RenderActionRecord(7) {}
+	virtual ~WordFindPuzzle() {}
+
+	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 "WordFindPuzzle"; }
+
+	struct Word {
+		Common::Path gridImageName;		// +0x55 - the letter grid for this word
+		Common::Path overlayImageName;	// +0x76 - overlay drawn on top
+		Common::Path animName;			// +0x97 - the "found" animation (a movie)
+		Common::Array<Common::Point> answerCoords;	// +0xb8 - the correct letter path (grid cells)
+		int16 eventFlagLabel = -1;		// +0x10a - set when the word is found
+		Common::Array<Common::Rect> letterRects;	// +0x10c - clickable letter cells
+	};
+
+	// Grid (col,row) of a letter rect, using the same pixel maths as the original.
+	Common::Point gridCoord(const Common::Rect &rect) const;
+	bool isAdjacent(const Common::Point &a, const Common::Point &b) const;
+	int letterAtCursor(const Common::Point &mousePos) const;
+	int numSelected() const;
+	bool chainMatchesAnswer() const;
+	void loadWord();
+	void startFoundAnimation();
+	void finishCurrentWord();
+	void redraw();
+	WordFindPuzzleData *getPuzzleData() const;
+
+	// -- File data --
+	uint16 _cursorType = 0;			// base 0x00
+	uint16 _cellGapX = 0;			// base 0x06 - added to a letter's width to get the column pitch
+	uint16 _cellGapY = 0;			// base 0x08 - row pitch component
+	SceneChangeDescription _solveScene;		// base 0x2a - shown once every word is found
+	Common::Path _solutionImageName;		// base 0x2f
+	Common::Array<Word> _words;
+
+	Common::Rect _exitHotspot;
+	uint16 _exitCursorType = 0;
+	SceneChangeDescription _exitScene;
+
+	Common::Array<RandomSoundBlock> _sounds;	// 4 blocks
+
+	// -- Runtime state --
+	// The active word persists across scene visits (and saves) via WordFindPuzzleData.
+	int _currentWord = 0;
+	Common::Array<bool> _selected;	// per letter of the current word
+	bool _allFound = false;
+
+	// The "found word" animation that plays before advancing to the next word.
+	MoviePlayer _movie;
+	bool _playingMovie = false;
+
+	Graphics::ManagedSurface _gridImage;
+	Graphics::ManagedSurface _overlayImage;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_WORDFINDPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 75e044b8228..97625d02f65 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -70,6 +70,7 @@ MODULE_OBJS = \
   action/puzzle/twodialpuzzle.o \
   action/puzzle/typingquizpuzzle.o \
   action/puzzle/whalesurvivorpuzzle.o \
+  action/puzzle/wordfindpuzzle.o \
   ui/fullscreenimage.o \
   ui/animatedbutton.o \
   ui/button.o \
diff --git a/engines/nancy/puzzledata.cpp b/engines/nancy/puzzledata.cpp
index fb27477f6db..e4f0036d52d 100644
--- a/engines/nancy/puzzledata.cpp
+++ b/engines/nancy/puzzledata.cpp
@@ -409,8 +409,14 @@ void TaskbarData::synchronize(Common::Serializer &ser) {
 	}
 }
 
+void WordFindPuzzleData::synchronize(Common::Serializer &ser) {
+	ser.syncAsSint16LE(currentWord);
+}
+
 PuzzleData *makePuzzleData(const uint32 tag) {
 	switch(tag) {
+	case WordFindPuzzleData::getTag():
+		return new WordFindPuzzleData();
 	case SliderPuzzleData::getTag():
 		return new SliderPuzzleData();
 	case RippedLetterPuzzleData::getTag():
diff --git a/engines/nancy/puzzledata.h b/engines/nancy/puzzledata.h
index 429cf6b94b0..1d8965de105 100644
--- a/engines/nancy/puzzledata.h
+++ b/engines/nancy/puzzledata.h
@@ -320,6 +320,19 @@ struct TaskbarData : public PuzzleData {
 	bool notifications[kNumButtons][kNumNotificationSubCategories] = {};
 };
 
+// Nancy13+ WordFindPuzzle (AR 170). The puzzle is solved one word at a time across
+// several scene visits; this remembers which word is currently active so progress
+// survives leaving and re-entering the scene (and saving/loading).
+struct WordFindPuzzleData : public PuzzleData {
+	WordFindPuzzleData() {}
+	virtual ~WordFindPuzzleData() {}
+
+	static constexpr uint32 getTag() { return MKTAG('W', 'F', 'N', 'D'); }
+	virtual void synchronize(Common::Serializer &ser);
+
+	int16 currentWord = 0;
+};
+
 PuzzleData *makePuzzleData(const uint32 tag);
 
 } // End of namespace Nancy




More information about the Scummvm-git-logs mailing list