[Scummvm-git-logs] scummvm master -> 6955edc63fde2b83550adceece7841f7248426ee
bluegr
noreply at scummvm.org
Tue Aug 4 22:49:05 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
232744417f NANCY: NANCY12: Use the correct cursors for RippedLetterPuzzle
6955edc63f NANCY: NANCY14: Implement HangmanPuzzle
Commit: 232744417f9cffbdefa024ce0ef67a55797c227a
https://github.com/scummvm/scummvm/commit/232744417f9cffbdefa024ce0ef67a55797c227a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-05T01:48:54+03:00
Commit Message:
NANCY: NANCY12: Use the correct cursors for RippedLetterPuzzle
The cursor check is for Nancy10 and newer games, but this puzzle is
only present in Nancy12 (it wasn't used in Nancy10 or 11)
Changed paths:
engines/nancy/action/puzzle/rippedletterpuzzle.cpp
engines/nancy/cursor.cpp
engines/nancy/cursor.h
diff --git a/engines/nancy/action/puzzle/rippedletterpuzzle.cpp b/engines/nancy/action/puzzle/rippedletterpuzzle.cpp
index 74990ba1e82..114396f99f7 100644
--- a/engines/nancy/action/puzzle/rippedletterpuzzle.cpp
+++ b/engines/nancy/action/puzzle/rippedletterpuzzle.cpp
@@ -325,6 +325,13 @@ void RippedLetterPuzzle::handleInput(NancyInput &input) {
return;
}
+ // Nancy10+ uses the dedicated puzzle rotate/hand cursors; earlier games
+ // reuse the generic rotate and hotspot cursors.
+ const bool useNewCursors = g_nancy->getGameType() >= kGameTypeNancy10;
+ const CursorManager::CursorType rotateCursor = useNewCursors ? CursorManager::kNewRotatePiece : CursorManager::kRotateCW;
+ const CursorManager::CursorType takeCursor = useNewCursors ? CursorManager::kNewUseHandHotspot : CursorManager::kHotspot;
+ const CursorManager::CursorType dropCursor = useNewCursors ? CursorManager::kNewUseHand : CursorManager::kHotspot;
+
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
Common::Rect screenHotspot = NancySceneState.getViewport().convertViewportToScreen(_destRects[i]);
if (screenHotspot.contains(input.mousePos)) {
@@ -337,7 +344,7 @@ void RippedLetterPuzzle::handleInput(NancyInput &input) {
insideRect.translate(screenHotspot.left, screenHotspot.top);
if (_rotationType != kRotationNone && insideRect.contains(input.mousePos)) {
- g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
+ g_nancy->_cursor->setCursorType(rotateCursor);
if (input.input & NancyInput::kLeftMouseButtonUp) {
// Player has clicked, rotate the piece
@@ -358,7 +365,7 @@ void RippedLetterPuzzle::handleInput(NancyInput &input) {
insideRect.translate(screenHotspot.left, screenHotspot.top);
if (insideRect.contains(input.mousePos)) {
- g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ g_nancy->_cursor->setCursorType(takeCursor);
if (input.input & NancyInput::kLeftMouseButtonUp) {
// Player has clicked, take the piece
@@ -395,7 +402,7 @@ void RippedLetterPuzzle::handleInput(NancyInput &input) {
insideRect.translate(screenHotspot.left, screenHotspot.top);
if (insideRect.contains(input.mousePos)) {
- g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ g_nancy->_cursor->setCursorType(dropCursor);
if (input.input & NancyInput::kLeftMouseButtonUp) {
// Player has clicked, drop the piece and pick up a new one
diff --git a/engines/nancy/cursor.cpp b/engines/nancy/cursor.cpp
index 24ec422d61d..13e8d4fdaa4 100644
--- a/engines/nancy/cursor.cpp
+++ b/engines/nancy/cursor.cpp
@@ -184,6 +184,9 @@ uint CursorManager::resolveNancy10CursorID(CursorType type, int16 itemID, bool s
case kInvertedRotateRight: return kNewInvertedRotateRight;
case kInvertedRotateLeft: return kNewInvertedRotateLeft;
case kDragHand: return kNewDragHand;
+ case kNewUseHand: return kNewUseHand;
+ case kNewUseHandHotspot: return kNewUseHandHotspot;
+ case kNewRotatePiece: return kNewRotatePiece;
case kPuzzleArrow: return kNewPuzzleArrow;
case kNewPuzzleSlideUp: return kNewPuzzleSlideUp;
case kNewPuzzleSlideDown: return kNewPuzzleSlideDown;
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index f8824feb022..429ffedd92b 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -90,8 +90,10 @@ public:
kNewRotateLeft = 30, // Type 15 â 360 scenes
kNewInvertedRotateRight = 32, // Type 16 â Inverted 360 rotation
kNewInvertedRotateLeft = 34, // Type 17 â Inverted 360 rotation
- kNewUseHand = 36, // Type 18 â Hand used while using items
+ kNewUseHand = 36, // Type 18 â Hand used while using items, and while carrying a puzzle piece
+ kNewUseHandHotspot = 37, // Type 18 hotspot â Hand shown when hovering a piece that can be picked up
kNewDragHand = 38, // Type 19 â Hand used while dragging puzzle pieces (e.g. SortPuzzle pickup action sets this)
+ kNewRotatePiece = 40, // Type 20 â Rotate arrows shown over a rotatable puzzle piece
kNewDialCW = 41, // Type 20 hotspot â Dial turn cursors, used by SafeDialPuzzle
kNewDialCCW = 43, // Type 21 hotspot
kNewPuzzleArrow = 45, // Type 22 hotspot â Arrow cursor shown when hovering a clickable puzzle hotspot
Commit: 6955edc63fde2b83550adceece7841f7248426ee
https://github.com/scummvm/scummvm/commit/6955edc63fde2b83550adceece7841f7248426ee
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-05T01:48:55+03:00
Commit Message:
NANCY: NANCY14: Implement HangmanPuzzle
Classic hangman, new in Nancy14 (AR 177). A word is drawn from a bank
and shown as a row of blanks; the player clicks the a-z letter tiles to
guess. A correct letter fills every matching blank; a wrong one draws
the next hang-stage piece. The word is fully revealed -> win; the hang
figure is completed (wrong guesses == number of hang pieces) -> lose.
The chosen word is remembered across visits so it is not immediately
repeated.
Changed paths:
A engines/nancy/action/puzzle/hangmanpuzzle.cpp
A engines/nancy/action/puzzle/hangmanpuzzle.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 452cbef1ea9..9c4beaa3188 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -51,6 +51,7 @@
#include "engines/nancy/action/puzzle/gridmappuzzle.h"
#include "engines/nancy/action/puzzle/matchpuzzle.h"
#include "engines/nancy/action/puzzle/hamradiopuzzle.h"
+#include "engines/nancy/action/puzzle/hangmanpuzzle.h"
#include "engines/nancy/action/puzzle/leverpuzzle.h"
#include "engines/nancy/action/puzzle/magnetmazepuzzle.h"
#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
@@ -505,9 +506,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
case 176:
return new DropSortPuzzle(); // conveyor-belt candy sorting
// -- Nancy14 new puzzles (types 177-182) --
- case 177: // HangmanPuzzle
- // TODO: not yet implemented
- return nullptr;
+ case 177:
+ return new HangmanPuzzle();
case 178: // AdjustPuzzle
// TODO: not yet implemented
return nullptr;
diff --git a/engines/nancy/action/puzzle/hangmanpuzzle.cpp b/engines/nancy/action/puzzle/hangmanpuzzle.cpp
new file mode 100644
index 00000000000..b64a1382ea7
--- /dev/null
+++ b/engines/nancy/action/puzzle/hangmanpuzzle.cpp
@@ -0,0 +1,331 @@
+/* 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/cursor.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/puzzledata.h"
+
+#include "engines/nancy/action/puzzle/hangmanpuzzle.h"
+
+#include "engines/nancy/state/scene.h"
+
+namespace Nancy {
+namespace Action {
+
+void HangmanPuzzle::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _puzzleImageName); // 0x3d
+ readFilename(stream, _lettersImageName); // 0x41
+ _field45 = stream.readSint16LE(); // 0x45
+
+ int16 numWords = stream.readSint16LE();
+ _words.resize(numWords);
+ for (int16 i = 0; i < numWords; ++i) {
+ readFilename(stream, _words[i]);
+ }
+
+ int16 numHangPieces = stream.readSint16LE();
+ readRectArray(stream, _hangPieceRects, numHangPieces); // 0x57
+ readRect(stream, _boardRect); // 0x67
+
+ int16 numSlots = stream.readSint16LE();
+ readRectArray(stream, _letterSlotRects, numSlots); // 0xae
+ int16 numGuessed = stream.readSint16LE();
+ readRectArray(stream, _guessedRowRects, numGuessed); // 0xbe
+
+ int16 numLetters = stream.readSint16LE();
+ _letters.resize(numLetters); // 0x77
+ for (int16 i = 0; i < numLetters; ++i) {
+ LetterTile &tile = _letters[i];
+ tile.letter = (char)stream.readByte();
+ readRect(stream, tile.idleRect);
+ readRect(stream, tile.hoverRect);
+ readRect(stream, tile.glyphRect);
+ readFilename(stream, tile.name);
+ }
+
+ _fieldCE = stream.readSint16LE(); // 0xce
+ _fieldD0 = stream.readSint32LE(); // 0xd0
+ _fieldD4 = stream.readSint16LE(); // 0xd4
+
+ for (uint i = 0; i < 3; ++i) { // 0x12c/0x182/0x1d8
+ _sounds[i].readData(stream);
+ }
+
+ readFilename(stream, _soundName); // 0x236
+
+ SceneOutcome *outcomes[] = { &_winScene, &_winScene2, &_loseScene };
+ for (SceneOutcome *outcome : outcomes) {
+ outcome->sceneID = stream.readSint16LE();
+ outcome->frameID = stream.readSint16LE();
+ outcome->flag.label = stream.readSint16LE();
+ outcome->flag.flag = stream.readByte();
+ outcome->sound.readData(stream);
+ }
+
+ // Trailing block read by the base record reader (25 bytes).
+ _tailField0 = stream.readSint32LE();
+ _tailField1 = stream.readSint16LE();
+ _tailVector[0] = stream.readSint32LE();
+ _tailVector[1] = stream.readSint32LE();
+ _tailVector[2] = stream.readSint32LE();
+ _tailField2 = stream.readSint16LE();
+ _tailSceneID = stream.readSint16LE();
+ _tailFlag = stream.readSint16LE();
+ _tailByte = stream.readByte();
+}
+
+HangmanData *HangmanPuzzle::getPuzzleData() const {
+ return (HangmanData *)NancySceneState.getPuzzleData(HangmanData::getTag());
+}
+
+void HangmanPuzzle::pickWord() {
+ HangmanData *data = getPuzzleData();
+
+ // Words not yet used this cycle; once all are used, start over.
+ Common::Array<uint> available;
+ for (uint i = 0; i < _words.size(); ++i) {
+ bool used = false;
+ if (data) {
+ for (const Common::String &w : data->usedWords) {
+ if (w == _words[i]) {
+ used = true;
+ break;
+ }
+ }
+ }
+ if (!used) {
+ available.push_back(i);
+ }
+ }
+
+ if (available.empty()) {
+ if (data) {
+ data->usedWords.clear();
+ }
+ for (uint i = 0; i < _words.size(); ++i) {
+ available.push_back(i);
+ }
+ }
+
+ if (available.empty()) {
+ return;
+ }
+
+ uint pick = available[g_nancy->_randomSource->getRandomNumber(available.size() - 1)];
+ _word = _words[pick];
+ if (data) {
+ data->usedWords.push_back(_word);
+ }
+}
+
+void HangmanPuzzle::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(_puzzleImageName, _puzzleImage);
+ g_nancy->_resource->loadImage(_lettersImageName, _lettersImage);
+ _puzzleImage.setTransparentColor(_drawSurface.getTransparentColor());
+ _lettersImage.setTransparentColor(_drawSurface.getTransparentColor());
+
+ pickWord();
+ _guessed.clear();
+ _revealed.clear();
+ _revealed.resize(_word.size(), false);
+ _wrongCount = 0;
+ _hoverTile = -1;
+ _solved = false;
+ _lost = false;
+ _outcomeApplied = false;
+
+ redraw();
+}
+
+int HangmanPuzzle::tileAtCursor(const Common::Point &mousePos) const {
+ for (uint i = 0; i < _letters.size(); ++i) {
+ if (_letters[i].idleRect.isEmpty()) {
+ continue;
+ }
+ if (NancySceneState.getViewport().convertViewportToScreen(_letters[i].idleRect).contains(mousePos)) {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+Common::Rect HangmanPuzzle::glyphForLetter(char letter) const {
+ for (const LetterTile &tile : _letters) {
+ if (tile.letter == letter) {
+ return tile.glyphRect;
+ }
+ }
+ return Common::Rect();
+}
+
+// Blits srcRect from src at destPos, clipping srcRect to the source image so an
+// out-of-bounds sprite rect (or an image that failed to load) can't read past
+// the surface.
+void HangmanPuzzle::safeBlit(const Graphics::ManagedSurface &src, const Common::Rect &srcRect, const Common::Point &destPos) {
+ if (src.w == 0 || src.h == 0 || srcRect.isEmpty()) {
+ return;
+ }
+
+ Common::Rect clipped = srcRect.findIntersectingRect(Common::Rect(src.w, src.h));
+ if (clipped.isEmpty()) {
+ return;
+ }
+
+ Common::Point dst(destPos.x + (clipped.left - srcRect.left), destPos.y + (clipped.top - srcRect.top));
+ _drawSurface.blitFrom(src, clipped, dst);
+}
+
+void HangmanPuzzle::redraw() {
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+
+ // The puzzle images are sprite sheets: the source rect indexes a sprite in
+ // the sheet, the destination is a separate on-screen position.
+
+ // A guessed ("used") tile stamps its used-mark sprite (2nd tile rect, from
+ // the puzzle sheet) onto the tile's alphabet position (1st tile rect).
+ for (const LetterTile &tile : _letters) {
+ if (tile.used) {
+ safeBlit(_puzzleImage, tile.hoverRect, Common::Point(tile.idleRect.left, tile.idleRect.top));
+ }
+ }
+
+ // Guessed-letters row: each played letter's glyph (from the letters sheet)
+ // at its slot in the row.
+ for (uint i = 0; i < _guessed.size() && i < _guessedRowRects.size(); ++i) {
+ safeBlit(_lettersImage, glyphForLetter(_guessed[i]),
+ Common::Point(_guessedRowRects[i].left, _guessedRowRects[i].top));
+ }
+
+ // Revealed word letters in their blanks.
+ for (uint i = 0; i < _revealed.size() && i < _letterSlotRects.size(); ++i) {
+ if (_revealed[i]) {
+ safeBlit(_lettersImage, glyphForLetter(_word[i]),
+ Common::Point(_letterSlotRects[i].left, _letterSlotRects[i].top));
+ }
+ }
+
+ // The hang figure: the current cumulative stage sprite (from the sheet's
+ // stage strip) drawn at the board position.
+ if (_wrongCount > 0 && _wrongCount <= (int)_hangPieceRects.size()) {
+ safeBlit(_puzzleImage, _hangPieceRects[_wrongCount - 1],
+ Common::Point(_boardRect.left, _boardRect.top));
+ }
+
+ _needsRedraw = true;
+}
+
+void HangmanPuzzle::commitGuess(uint tileIndex) {
+ LetterTile &tile = _letters[tileIndex];
+ if (tile.used) {
+ return;
+ }
+ tile.used = true;
+
+ char c = tile.letter;
+ _guessed.push_back(c);
+
+ bool correct = false;
+ for (uint i = 0; i < _word.size(); ++i) {
+ if (_word[i] == c) {
+ _revealed[i] = true;
+ correct = true;
+ }
+ }
+
+ if (!correct) {
+ ++_wrongCount;
+ }
+
+ bool allRevealed = !_revealed.empty();
+ for (uint i = 0; i < _revealed.size(); ++i) {
+ if (!_revealed[i]) {
+ allRevealed = false;
+ break;
+ }
+ }
+
+ if (allRevealed) {
+ _solved = true;
+ } else if (_wrongCount >= (int)_hangPieceRects.size()) {
+ _lost = true;
+ }
+
+ redraw();
+}
+
+void HangmanPuzzle::applyOutcome(const SceneOutcome &outcome) {
+ SceneChangeDescription desc;
+ desc.sceneID = outcome.sceneID;
+ desc.frameID = outcome.frameID;
+ NancySceneState.changeScene(desc);
+ NancySceneState.setEventFlag(outcome.flag);
+}
+
+void HangmanPuzzle::handleInput(NancyInput &input) {
+ if (_state != kRun || _solved || _lost) {
+ return;
+ }
+
+ int tile = tileAtCursor(input.mousePos);
+ if (tile >= 0 && !_letters[tile].used) {
+ // Clickable-hotspot cursor for puzzles (the blue pointing hand).
+ g_nancy->_cursor->setCursorType(CursorManager::kPuzzleArrow);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ commitGuess((uint)tile);
+ }
+ input.eatMouseInput();
+ }
+}
+
+void HangmanPuzzle::execute() {
+ switch (_state) {
+ case kBegin:
+ init();
+ registerGraphics();
+ _state = kRun;
+ break;
+ case kRun:
+ if ((_solved || _lost) && !_outcomeApplied) {
+ _outcomeApplied = true;
+ applyOutcome(_solved ? _winScene : _loseScene);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/hangmanpuzzle.h b/engines/nancy/action/puzzle/hangmanpuzzle.h
new file mode 100644
index 00000000000..f167d67545d
--- /dev/null
+++ b/engines/nancy/action/puzzle/hangmanpuzzle.h
@@ -0,0 +1,137 @@
+/* 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_HANGMANPUZZLE_H
+#define NANCY_ACTION_HANGMANPUZZLE_H
+
+#include "engines/nancy/commontypes.h"
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+
+struct HangmanData;
+
+namespace Action {
+
+// Classic hangman, new in Nancy14 (AR 177). A word is drawn from a bank and
+// shown as a row of blanks; the player clicks the a-z letter tiles to guess.
+// A correct letter fills every matching blank; a wrong one draws the next
+// hang-stage piece. The word is fully revealed -> win; the hang figure is
+// completed (wrong guesses == number of hang pieces) -> lose. The chosen word
+// is remembered across visits so it is not immediately repeated.
+class HangmanPuzzle : public RenderActionRecord {
+public:
+ HangmanPuzzle() : RenderActionRecord(7) {}
+ virtual ~HangmanPuzzle() {}
+
+ 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 "HangmanPuzzle"; }
+
+ // One clickable letter tile. Its letter is the ASCII value stored in the
+ // record; idleRect/hoverRect are its states in the puzzle sprite sheet and
+ // glyphRect is the letter glyph (in the letters sprite sheet) that gets
+ // stamped into the word blanks / guessed row when this letter is played.
+ struct LetterTile {
+ char letter = 0;
+ Common::Rect idleRect;
+ Common::Rect hoverRect;
+ Common::Rect glyphRect;
+ Common::String name;
+ bool used = false;
+ };
+
+ // A win/lose transition stored at the tail of the record: a scene change
+ // with an event flag, each with its own random-sound block.
+ struct SceneOutcome {
+ int16 sceneID = 0;
+ int16 frameID = 0;
+ FlagDescription flag;
+ RandomSoundBlock sound;
+ };
+
+ HangmanData *getPuzzleData() const;
+ void pickWord();
+ Common::Rect glyphForLetter(char letter) const; // glyph rect of the tile for this letter, or empty
+ int tileAtCursor(const Common::Point &mousePos) const;
+ void commitGuess(uint tileIndex);
+ void safeBlit(const Graphics::ManagedSurface &src, const Common::Rect &srcRect, const Common::Point &destPos);
+ void redraw();
+ void applyOutcome(const SceneOutcome &outcome);
+
+ // -- File data --
+ Common::Path _puzzleImageName; // 0x3d
+ Common::Path _lettersImageName; // 0x41
+ int16 _field45 = 0; // 0x45
+
+ Common::Array<Common::String> _words; // candidate word bank
+ Common::Array<Common::Rect> _hangPieceRects; // 0x57, hang-stage pieces
+ Common::Rect _boardRect; // 0x67
+ Common::Array<Common::Rect> _letterSlotRects; // 0xae, word-blank positions
+ Common::Array<Common::Rect> _guessedRowRects; // 0xbe, guessed-letters row
+ Common::Array<LetterTile> _letters; // 0x77, the a-z tiles
+
+ int16 _fieldCE = 0; // 0xce
+ int32 _fieldD0 = 0; // 0xd0
+ int16 _fieldD4 = 0; // 0xd4
+
+ RandomSoundBlock _sounds[3]; // 0x12c/0x182/0x1d8, feedback sound blocks
+
+ Common::Path _soundName; // 0x236
+ SceneOutcome _winScene; // 0x290
+ SceneOutcome _winScene2; // 0x2f1
+ SceneOutcome _loseScene; // 0x352
+
+ // Trailing block read by the base record reader (vtable+0x24): a fixed
+ // 25-byte {int32, int16, 3x int32 vector, 2x int16, int16, byte} structure.
+ int32 _tailField0 = 0;
+ int16 _tailField1 = 0;
+ int32 _tailVector[3] = { 0, 0, 0 };
+ int16 _tailField2 = 0;
+ int16 _tailSceneID = 0;
+ int16 _tailFlag = 0;
+ byte _tailByte = 0;
+
+ // -- Runtime state --
+ Graphics::ManagedSurface _puzzleImage;
+ Graphics::ManagedSurface _lettersImage;
+
+ Common::String _word; // the word being guessed (lowercase)
+ Common::Array<char> _guessed; // letters played, in order
+ Common::Array<bool> _revealed; // per word position
+ int _wrongCount = 0;
+ int _hoverTile = -1;
+ bool _solved = false;
+ bool _lost = false;
+ bool _outcomeApplied = false;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_HANGMANPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 0a2c3644ef5..c82b8813691 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -34,6 +34,7 @@ MODULE_OBJS = \
action/puzzle/dropsortpuzzle.o \
action/puzzle/gridmappuzzle.o \
action/puzzle/hamradiopuzzle.o \
+ action/puzzle/hangmanpuzzle.o \
action/puzzle/leverpuzzle.o \
action/puzzle/magnetmazepuzzle.o \
action/puzzle/mazechasepuzzle.o \
diff --git a/engines/nancy/puzzledata.cpp b/engines/nancy/puzzledata.cpp
index d6e966c039e..9c1457140a9 100644
--- a/engines/nancy/puzzledata.cpp
+++ b/engines/nancy/puzzledata.cpp
@@ -478,6 +478,17 @@ void WordFindPuzzleData::synchronize(Common::Serializer &ser) {
ser.syncAsSint16LE(currentWord);
}
+void HangmanData::synchronize(Common::Serializer &ser) {
+ uint16 count = usedWords.size();
+ ser.syncAsUint16LE(count);
+ if (ser.isLoading()) {
+ usedWords.resize(count);
+ }
+ for (uint i = 0; i < count; ++i) {
+ ser.syncString(usedWords[i]);
+ }
+}
+
void DrivingData::synchronize(Common::Serializer &ser) {
ser.syncAsByte(valid);
ser.syncAsSint32LE(carX);
@@ -493,6 +504,8 @@ PuzzleData *makePuzzleData(const uint32 tag) {
return new DrivingData();
case WordFindPuzzleData::getTag():
return new WordFindPuzzleData();
+ case HangmanData::getTag():
+ return new HangmanData();
case SliderPuzzleData::getTag():
return new SliderPuzzleData();
case RippedLetterPuzzleData::getTag():
diff --git a/engines/nancy/puzzledata.h b/engines/nancy/puzzledata.h
index bd0b4e3b755..3a42c7820f6 100644
--- a/engines/nancy/puzzledata.h
+++ b/engines/nancy/puzzledata.h
@@ -379,6 +379,20 @@ struct WordFindPuzzleData : public PuzzleData {
int16 currentWord = 0;
};
+// Nancy14+ HangmanPuzzle (AR 177). Remembers which words have already been used
+// so a fresh visit picks a new one; once every word has been used the set is
+// cleared and words become available again (matching the original's word-reuse
+// bitmap). Persisted so progress survives leaving the scene and saving.
+struct HangmanData : public PuzzleData {
+ HangmanData() {}
+ virtual ~HangmanData() {}
+
+ static constexpr uint32 getTag() { return MKTAG('H', 'A', 'N', 'G'); }
+ virtual void synchronize(Common::Serializer &ser);
+
+ Common::Array<Common::String> usedWords;
+};
+
// Nancy12 DrivingPuzzle (AR 160). The car's position, heading and tire state persist
// across visits to the driving map (driving into a location, then coming back), matching
// the original's retainState mechanism, which saves the car to globals every frame and
More information about the Scummvm-git-logs
mailing list