[Scummvm-git-logs] scummvm master -> 0c77e51249fca8bdf63981a126300fdf0f9fb771
bluegr
noreply at scummvm.org
Wed Aug 19 22:34:33 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:
5c6346d8bb NANCY: NANCY14: Remove now obsolete function from HangmanPuzzle
0c77e51249 NANCY: NANCY14: Implement AdjustPuzzle
Commit: 5c6346d8bb878d7b04e56df6c69b0aa4a1ee4b4b
https://github.com/scummvm/scummvm/commit/5c6346d8bb878d7b04e56df6c69b0aa4a1ee4b4b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T01:27:52+03:00
Commit Message:
NANCY: NANCY14: Remove now obsolete function from HangmanPuzzle
This was used during development, and is no longer needed
Changed paths:
engines/nancy/action/puzzle/hangmanpuzzle.cpp
engines/nancy/action/puzzle/hangmanpuzzle.h
diff --git a/engines/nancy/action/puzzle/hangmanpuzzle.cpp b/engines/nancy/action/puzzle/hangmanpuzzle.cpp
index 1bec805cf24..dada33264a1 100644
--- a/engines/nancy/action/puzzle/hangmanpuzzle.cpp
+++ b/engines/nancy/action/puzzle/hangmanpuzzle.cpp
@@ -201,23 +201,6 @@ Common::Rect HangmanPuzzle::glyphForLetter(char letter) const {
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());
@@ -228,21 +211,21 @@ void HangmanPuzzle::redraw() {
// 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));
+ _drawSurface.blitFrom(_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]),
+ _drawSurface.blitFrom(_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]),
+ _drawSurface.blitFrom(_lettersImage, glyphForLetter(_word[i]),
Common::Point(_letterSlotRects[i].left, _letterSlotRects[i].top));
}
}
@@ -250,7 +233,7 @@ void HangmanPuzzle::redraw() {
// 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],
+ _drawSurface.blitFrom(_puzzleImage, _hangPieceRects[_wrongCount - 1],
Common::Point(_boardRect.left, _boardRect.top));
}
diff --git a/engines/nancy/action/puzzle/hangmanpuzzle.h b/engines/nancy/action/puzzle/hangmanpuzzle.h
index 3d8992cdbd1..34632972fb8 100644
--- a/engines/nancy/action/puzzle/hangmanpuzzle.h
+++ b/engines/nancy/action/puzzle/hangmanpuzzle.h
@@ -80,7 +80,6 @@ protected:
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);
Commit: 0c77e51249fca8bdf63981a126300fdf0f9fb771
https://github.com/scummvm/scummvm/commit/0c77e51249fca8bdf63981a126300fdf0f9fb771
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T01:30:40+03:00
Commit Message:
NANCY: NANCY14: Implement AdjustPuzzle
This is a simple puzzle, used when calibrating the plotter
Changed paths:
A engines/nancy/action/puzzle/adjustpuzzle.cpp
A engines/nancy/action/puzzle/adjustpuzzle.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 0c414aeeb60..ab2a338fa10 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -32,6 +32,7 @@
#include "engines/nancy/action/secondaryvideo.h"
#include "engines/nancy/action/secondarymovie.h"
+#include "engines/nancy/action/puzzle/adjustpuzzle.h"
#include "engines/nancy/action/puzzle/angletosspuzzle.h"
#include "engines/nancy/action/puzzle/arcadepuzzle.h"
#include "engines/nancy/action/puzzle/assemblypuzzle.h"
@@ -509,9 +510,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
// -- Nancy14 new puzzles (types 177-182) --
case 177:
return new HangmanPuzzle();
- case 178: // AdjustPuzzle
- // TODO: not yet implemented
- return nullptr;
+ case 178:
+ return new AdjustPuzzle();
case 179: // MeterPuzzle
// TODO: not yet implemented
return nullptr;
diff --git a/engines/nancy/action/puzzle/adjustpuzzle.cpp b/engines/nancy/action/puzzle/adjustpuzzle.cpp
new file mode 100644
index 00000000000..7280b91cb50
--- /dev/null
+++ b/engines/nancy/action/puzzle/adjustpuzzle.cpp
@@ -0,0 +1,308 @@
+/* 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/util.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/action/puzzle/adjustpuzzle.h"
+
+#include "engines/nancy/state/scene.h"
+
+namespace Nancy {
+namespace Action {
+
+void AdjustPuzzle::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _imageName); // 0x1c0
+ _field3d = stream.readSint16LE(); // 0x3d
+
+ int16 numPieces = stream.readSint16LE();
+ _pieces.resize(numPieces); // 0x3f
+ for (int16 i = 0; i < numPieces; ++i) {
+ Piece &piece = _pieces[i];
+ for (int j = 0; j < 4; ++j) {
+ readRect(stream, piece.rects[j]);
+ }
+ int16 numSubRects = stream.readSint16LE();
+ readRectArray(stream, piece.subRects, numSubRects);
+ piece.initialState = stream.readByte();
+ readRect(stream, piece.boundRect);
+ }
+
+ _pieceSound.readData(stream); // 0x4f
+
+ readRect(stream, _rectA5); // 0xa5
+ readRect(stream, _rectB5); // 0xb5
+
+ _testSound.readData(stream); // 0xc5
+
+ readFilename(stream, _adjustName); // 0x11b
+ if (!_adjustName.empty()) {
+ _hasAdjustRect = true;
+ readRect(stream, _adjustRect); // 0x11f
+ _adjustSound.readData(stream); // 0x12f
+ }
+
+ _field18a = stream.readSint16LE(); // 0x18a
+
+ int16 numOverlays = stream.readSint16LE();
+ _overlayNames.resize(numOverlays); // 0x18c
+ for (int16 i = 0; i < numOverlays; ++i) {
+ readFilename(stream, _overlayNames[i]);
+ }
+
+ int16 numRows = stream.readSint16LE();
+ _matrix.resize(numRows); // 0x1b0
+ for (int16 i = 0; i < numRows; ++i) {
+ MatrixRow &row = _matrix[i];
+ row.cols.resize(numPieces);
+ for (int16 j = 0; j < numPieces; ++j) {
+ row.cols[j] = stream.readSint16LE();
+ }
+ row.result = stream.readByte();
+ }
+
+ _defaultResult = stream.readByte(); // 0x1c4
+ _perfectResult = stream.readByte(); // 0x1c5
+
+ Outcome *outcomes[] = { &_winScene, &_loseScene };
+ for (Outcome *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 count-prefixed array of 23-byte give-up hotspots
+ // {Rect, uint16 cursorType, uint16 sceneID, int16 flagLabel, byte flagValue}.
+ // The exit always jumps to the scene's first frame.
+ int16 numExitZones = stream.readSint16LE();
+ for (int16 i = 0; i < numExitZones; ++i) {
+ Common::Rect r;
+ readRect(stream, r);
+ uint16 cursorType = stream.readUint16LE();
+ uint16 sceneID = stream.readUint16LE();
+ int16 flagLabel = stream.readSint16LE();
+ byte flagValue = stream.readByte();
+
+ if (i == 0) {
+ _exitHotspot = r;
+ _exitCursorType = cursorType;
+ _exitScene.sceneID = sceneID;
+ _exitScene.frameID = 0;
+ _exitFlag.label = flagLabel;
+ _exitFlag.flag = flagValue;
+ }
+ }
+}
+
+void AdjustPuzzle::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());
+
+ _overlayImages.resize(_overlayNames.size());
+ for (uint i = 0; i < _overlayNames.size(); ++i) {
+ if (!_overlayNames[i].empty()) {
+ g_nancy->_resource->loadImage(_overlayNames[i], _overlayImages[i]);
+ _overlayImages[i].setTransparentColor(_drawSurface.getTransparentColor());
+ }
+ }
+
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ _pieces[i].state = _pieces[i].initialState;
+ }
+
+ _resultIndex = 0;
+ _showResult = false;
+ _solved = false;
+ _lost = false;
+ _outcomeApplied = false;
+
+ redraw();
+}
+
+// Returns the piece whose +/- control is under the cursor, and sets delta to
+// +1 (increment) or -1 (decrement). Returns -1 if none. rects[1]/rects[3] are the
+// on-screen down/up arrow hotspots (rects[0]/rects[2] are the arrow sprites in the
+// image sheet).
+int AdjustPuzzle::pieceControlAtCursor(const Common::Point &mousePos, int &delta) const {
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ const Piece &piece = _pieces[i];
+ if (!piece.rects[3].isEmpty() &&
+ NancySceneState.getViewport().convertViewportToScreen(piece.rects[3]).contains(mousePos)) {
+ delta = 1; // up arrow
+ return (int)i;
+ }
+ if (!piece.rects[1].isEmpty() &&
+ NancySceneState.getViewport().convertViewportToScreen(piece.rects[1]).contains(mousePos)) {
+ delta = -1; // down arrow
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+byte AdjustPuzzle::evaluateResult() const {
+ for (uint r = 0; r < _matrix.size(); ++r) {
+ const MatrixRow &row = _matrix[r];
+ bool match = true;
+ for (uint j = 0; j < _pieces.size() && j < row.cols.size(); ++j) {
+ if (row.cols[j] != _pieces[j].state) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ return row.result;
+ }
+ }
+ return _defaultResult;
+}
+
+void AdjustPuzzle::runTest() {
+ _resultIndex = evaluateResult();
+ _showResult = true;
+
+ if (_resultIndex == _perfectResult) {
+ _solved = true;
+ } else if (_loseScene.sceneID != kNoScene) {
+ // A lose scene is configured (kNoScene means "stay and let the player retry").
+ _lost = true;
+ }
+
+ redraw();
+}
+
+void AdjustPuzzle::redraw() {
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+
+ if (_showResult && !_solved && _resultIndex >= 1 && (uint)(_resultIndex - 1) < _overlayImages.size()) {
+ // Result screen: the matched result overlay (a full-frame _TXT overlay).
+ const Graphics::ManagedSurface &overlay = _overlayImages[_resultIndex - 1];
+ _drawSurface.blitFrom(overlay, Common::Rect(overlay.w, overlay.h), Common::Point(0, 0));
+ } else {
+ // Levels run 1..5; level 1 is the default (from the scene background), and
+ // levels 2..5 show the sheet sprite subRects[level - 2] at the piece's
+ // on-screen bounding rect.
+ for (const Piece &piece : _pieces) {
+ int idx = piece.state - 2;
+ if (piece.state == 1 || idx < 0 || (uint)idx >= piece.subRects.size()) {
+ continue;
+ }
+ _drawSurface.blitFrom(_image, piece.subRects[idx],
+ Common::Point(piece.boundRect.left, piece.boundRect.top));
+ }
+ }
+
+ _needsRedraw = true;
+}
+
+void AdjustPuzzle::applyOutcome(const Outcome &outcome) {
+ SceneChangeDescription desc;
+ desc.sceneID = outcome.sceneID;
+ desc.frameID = outcome.frameID;
+ NancySceneState.changeScene(desc);
+ NancySceneState.setEventFlag(outcome.flag);
+}
+
+void AdjustPuzzle::handleInput(NancyInput &input) {
+ if (_state != kRun || _solved || _lost) {
+ return;
+ }
+
+ const bool click = (input.input & NancyInput::kLeftMouseButtonUp) != 0;
+
+ // Give-up hotspot: leave the puzzle.
+ if (!_exitHotspot.isEmpty() &&
+ NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+ g_nancy->_cursor->setCursorType((CursorManager::CursorType)_exitCursorType, true);
+ if (click) {
+ _exitRequested = true;
+ }
+ input.eatMouseInput();
+ return;
+ }
+
+ int delta = 0;
+ int piece = pieceControlAtCursor(input.mousePos, delta);
+ if (piece >= 0) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ if (click) {
+ // Levels run from 1 (default) up to 1 + the number of level sprites.
+ int maxState = 1 + (int)_pieces[piece].subRects.size();
+ _pieces[piece].state = CLIP<int>(_pieces[piece].state + delta, 1, maxState);
+ _showResult = false;
+ redraw();
+ }
+ input.eatMouseInput();
+ return;
+ }
+
+ // The "test" control: evaluate the current settings.
+ if (!_rectB5.isEmpty() &&
+ NancySceneState.getViewport().convertViewportToScreen(_rectB5).contains(input.mousePos)) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ if (click) {
+ runTest();
+ }
+ input.eatMouseInput();
+ }
+}
+
+void AdjustPuzzle::execute() {
+ switch (_state) {
+ case kBegin:
+ init();
+ registerGraphics();
+ _state = kRun;
+ break;
+ case kRun:
+ if (_exitRequested) {
+ NancySceneState.setEventFlag(_exitFlag);
+ NancySceneState.changeScene(_exitScene);
+ break;
+ }
+ 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/adjustpuzzle.h b/engines/nancy/action/puzzle/adjustpuzzle.h
new file mode 100644
index 00000000000..7a486f270b9
--- /dev/null
+++ b/engines/nancy/action/puzzle/adjustpuzzle.h
@@ -0,0 +1,128 @@
+/* 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_ADJUSTPUZZLE_H
+#define NANCY_ACTION_ADJUSTPUZZLE_H
+
+#include "engines/nancy/commontypes.h"
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+// Adjustment puzzle, new in Nancy14 (AR 178). Used for the plotter-repair
+// puzzle: several elements each have a state the player nudges up/down with
+// +/- controls. Testing the current settings matches the state tuple against a
+// table of combinations to pick a result; hitting the "perfect" result solves
+// the puzzle, any other shows a result overlay (Perfect-Vert / -Hor / -Mirror /
+// -Lite, ...) and fails.
+class AdjustPuzzle : public RenderActionRecord {
+public:
+ AdjustPuzzle() : RenderActionRecord(7) {}
+ virtual ~AdjustPuzzle() {}
+
+ 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 "AdjustPuzzle"; }
+
+ // One adjustable element. rects[0]/rects[2] are the decrement/increment
+ // hotspots; subRects are the per-state display frames (blitted at boundRect).
+ struct Piece {
+ Common::Rect rects[4];
+ Common::Array<Common::Rect> subRects;
+ byte initialState = 0;
+ Common::Rect boundRect;
+ int state = 0;
+ };
+
+ // A row of the result table: the required state of each piece, plus the
+ // result index produced when the states match it.
+ struct MatrixRow {
+ Common::Array<int16> cols;
+ byte result = 0;
+ };
+
+ struct Outcome {
+ int16 sceneID = 0;
+ int16 frameID = 0;
+ FlagDescription flag;
+ RandomSoundBlock sound;
+ };
+
+ int pieceControlAtCursor(const Common::Point &mousePos, int &delta) const;
+ byte evaluateResult() const;
+ void runTest();
+ void redraw();
+ void applyOutcome(const Outcome &outcome);
+
+ // -- File data --
+ Common::Path _imageName; // 0x1c0
+ int16 _field3d = 0; // 0x3d
+ Common::Array<Piece> _pieces; // 0x3f
+
+ Common::Rect _rectA5; // 0xa5
+ Common::Rect _rectB5; // 0xb5, "test"/go control
+
+ RandomSoundBlock _pieceSound; // 0x4f, after the pieces
+ RandomSoundBlock _testSound; // 0xc5
+
+ Common::Path _adjustName; // 0x11b (plotter animation movie)
+ bool _hasAdjustRect = false;
+ Common::Rect _adjustRect; // 0x11f
+ RandomSoundBlock _adjustSound; // 0x12f (present only when _adjustName is set)
+
+ int16 _field18a = 0; // 0x18a
+ Common::Array<Common::Path> _overlayNames; // 0x18c, result overlays
+ Common::Array<MatrixRow> _matrix; // 0x1b0
+
+ byte _defaultResult = 0; // 0x1c4
+ byte _perfectResult = 0; // 0x1c5
+ Outcome _winScene; // 0x21c (field0), scene at 0x21e
+ Outcome _loseScene; // 0x27d (field0), scene at 0x27f
+
+ // Give-up hotspot (count-prefixed 23-byte trailer): click to leave the puzzle.
+ Common::Rect _exitHotspot;
+ uint16 _exitCursorType = 0;
+ SceneChangeDescription _exitScene;
+ FlagDescription _exitFlag;
+
+ // -- Runtime state --
+ Graphics::ManagedSurface _image;
+ Common::Array<Graphics::ManagedSurface> _overlayImages;
+ byte _resultIndex = 0;
+ bool _showResult = false;
+ bool _solved = false;
+ bool _lost = false;
+ bool _outcomeApplied = false;
+ bool _exitRequested = false;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_ADJUSTPUZZLE_H
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index fb5bee710f8..ba4c0173652 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -16,6 +16,7 @@ MODULE_OBJS = \
action/overlay.o \
action/secondarymovie.o \
action/secondaryvideo.o \
+ action/puzzle/adjustpuzzle.o \
action/puzzle/angletosspuzzle.o \
action/puzzle/arcadepuzzle.o \
action/puzzle/assemblypuzzle.o \
More information about the Scummvm-git-logs
mailing list