[Scummvm-git-logs] scummvm master -> 5cc80b20e0c695e0adbb6cf27743350962416df0
bluegr
noreply at scummvm.org
Sat Jun 27 09:50:52 UTC 2026
This automated email contains information about 11 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
a8494462b8 NANCY: Clean up comments
a583725952 NANCY: Proper implementation of the textbox used in Nancy10+
d715447239 NANCY: More comments cleanup
67e94fbbb4 NANCY: Add an enum and unify states in the Nancy10+ popups
2f8d211232 NANCY: Fix the hover cursor in the Nancy10+ inventory popup
aa33763e68 NANCY: Revert autotext lookup for sound names
40bb2c20a5 NANCY: Handle event flags and drop hardcoded data for Nancy12+ games
75c43fb022 DEVTOOLS: Remove static data for Nancy12+ games and regen nancy.dat
6f4bae2f16 NANCY: Fix hover cursor value checked for puzzles in Nancy10+
a35fb9aacd NANCY: Add another case for case-insensitive searching in Nancy11+
5cc80b20e0 NANCY: Cleanup comments
Commit: a8494462b8ffa52850db95da73f49a494d68b76a
https://github.com/scummvm/scummvm/commit/a8494462b8ffa52850db95da73f49a494d68b76a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:07+03:00
Commit Message:
NANCY: Clean up comments
Changed paths:
engines/nancy/action/puzzle/angletosspuzzle.cpp
engines/nancy/action/puzzle/angletosspuzzle.h
engines/nancy/action/puzzle/arcadepuzzle.cpp
engines/nancy/action/puzzle/arcadepuzzle.h
engines/nancy/action/puzzle/matchpuzzle.h
engines/nancy/action/puzzle/onebuildpuzzle.cpp
engines/nancy/action/puzzle/onebuildpuzzle.h
diff --git a/engines/nancy/action/puzzle/angletosspuzzle.cpp b/engines/nancy/action/puzzle/angletosspuzzle.cpp
index c22d17188d2..6f08628850f 100644
--- a/engines/nancy/action/puzzle/angletosspuzzle.cpp
+++ b/engines/nancy/action/puzzle/angletosspuzzle.cpp
@@ -46,7 +46,7 @@ void AngleTossPuzzle::init() {
// Draw the initial angle and power indicators.
// The throw button sprite is NOT drawn here â the static background already shows the
// idle button state. The sprite (data+0x4d) is the launched/pressed overlay, drawn
- // only when LAUNCH is clicked (mirroring the DAT_0059bfbd conditional in FUN_0044b1fa).
+ // only when LAUNCH is clicked.
_drawSurface.blitFrom(_image, _angleSprites[_curAngle], _angleDisplay);
_drawSurface.blitFrom(_image, _powerSprites[_curPower], _powerDisplay);
}
@@ -57,7 +57,7 @@ void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
// data+0x21..0x2c: 6 Ã uint16.
// _initialPower/_initialAngle: starting player position (copied to object+0x24/0x26 in original).
// _numPowers/_numAngles: UI control bounds.
- // _targetPower/_targetAngle: the correct answer for this round (compared in FUN_0044a6be).
+ // _targetPower/_targetAngle: the correct answer for this round.
_initialPower = stream.readUint16LE();
_initialAngle = stream.readUint16LE();
_numPowers = stream.readUint16LE();
@@ -113,7 +113,7 @@ void AngleTossPuzzle::execute() {
g_nancy->_sound->loadSound(_squeakSound);
g_nancy->_sound->loadSound(_chainSound);
- // FUN_0044a526: clear all result/hint flags before the player starts.
+ // Clear all result/hint flags before the player starts.
NancySceneState.setEventFlag(_powerTooStrongFlag, g_nancy->_false);
NancySceneState.setEventFlag(_powerTooWeakFlag, g_nancy->_false);
NancySceneState.setEventFlag(_angleTooLeftFlag, g_nancy->_false);
@@ -124,12 +124,12 @@ void AngleTossPuzzle::execute() {
break;
case kRun:
- // Wait for the chain sound to finish, then evaluate the throw (FUN_0044a6be)
+ // Wait for the chain sound to finish, then evaluate the throw
// and always transition to _throwSquidScene so the animation plays.
if (_isThrown && !g_nancy->_sound->isSoundPlaying(_chainSound)) {
_isThrown = false;
- // FUN_0044a6be: set exactly one result flag based on how accurate the throw was.
+ // Set exactly one result flag based on how accurate the throw was.
// The animation scene reads these flags to decide what to show.
if (_curPower == _targetPower && _curAngle == _targetAngle) {
// Exact match of power and angle â player wins round!
@@ -241,7 +241,7 @@ void AngleTossPuzzle::handleInput(NancyInput &input) {
g_nancy->_sound->playSound(_chainSound);
_isThrown = true;
- // Show the launched/pressed overlay sprite (DAT_0059bfbd != 0 path in FUN_0044b1fa).
+ // Show the launched/pressed overlay sprite.
_drawSurface.blitFrom(_image, _throwSprite, _throwDisplay);
_needsRedraw = true;
}
diff --git a/engines/nancy/action/puzzle/angletosspuzzle.h b/engines/nancy/action/puzzle/angletosspuzzle.h
index 95b5a94026c..4a83ee56432 100644
--- a/engines/nancy/action/puzzle/angletosspuzzle.h
+++ b/engines/nancy/action/puzzle/angletosspuzzle.h
@@ -41,7 +41,6 @@ namespace Action {
// Wrong throws transition to _throwSquidScene, showing the appropriate failure animation,
// based on which of the four fail flags is set (too strong/weak, too left/right). After the
// separate AR instances (each with their own target) implement the 3-round mechanic.
-// FUN_0044a526 and FUN_0044a6be handle flag clearing and result evaluation in the original.
class AngleTossPuzzle : public RenderActionRecord {
public:
@@ -64,7 +63,7 @@ protected:
// data+0x21..0x2c: 6 Ã uint16.
// _initialPower/_initialAngle: starting player selection (copied to object+0x24/0x26 in original).
// _numPowers/_numAngles: UI control bounds (always 5 in practice).
- // _targetPower/_targetAngle: the correct answer for this AR instance (compared in FUN_0044a6be).
+ // _targetPower/_targetAngle: the correct answer for this AR instance.
uint16 _initialPower = 0;
uint16 _initialAngle = 0;
uint16 _numPowers = 0;
@@ -73,7 +72,6 @@ protected:
uint16 _targetAngle = 0;
// The 22 rects read from the data file, in stream order.
- // Rect-to-data-offset mapping confirmed from the render callback (FUN_0044b1fa).
//
// Sprite rects (_fooSprite) are source areas within the loaded image.
// Display rects (_fooDisplay) are destination areas on the viewport overlay.
@@ -105,7 +103,7 @@ protected:
SoundDescription _squeakSound; // Played when changing aim angle
SoundDescription _chainSound; // Played when LAUNCH is pressed
- SceneChangeWithFlag _throwSquidScene; // Triggered on throw (FUN_0044a6be result)
+ SceneChangeWithFlag _throwSquidScene; // Triggered on throw
int16 _powerTooStrongFlag = -1; // 0x236
int16 _powerTooWeakFlag = -1; // 0x238
diff --git a/engines/nancy/action/puzzle/arcadepuzzle.cpp b/engines/nancy/action/puzzle/arcadepuzzle.cpp
index 7009ce0526c..0b3a55daec2 100644
--- a/engines/nancy/action/puzzle/arcadepuzzle.cpp
+++ b/engines/nancy/action/puzzle/arcadepuzzle.cpp
@@ -37,7 +37,7 @@ namespace Action {
// Wall normals for collision types 0..16.
// Each entry is {dx, dy, spin} matching original offsets at this+0x188 + type*0xc.
-// Type 0 = paddle center (handled specially by FUN_0044d03d â normal not used here).
+// Type 0 = paddle center (handled specially â normal not used here).
// Positive dy = up in "math" coords, i.e. y -= dy*speed on screen.
const float ArcadePuzzle::_wallNormals[17][3] = {
{ 0.0f, 1.0f, 0.0f }, // 0 paddle center (placeholder â handled separately)
@@ -250,7 +250,7 @@ void ArcadePuzzle::buildAngleTable() {
_angleTable[right * 6 + 2] = 0.0f;
// sub-entry 1: reflect across y-axis: (-dx, dy)
- // FUN_00447eb9 with no-negate: result = 2*(v·n)*n - v
+ // Reflection with no-negate: result = 2*(v·n)*n - v
// n=(0,1), v=(dx,dy): result = (-dx, dy)
float dot = dx * mirrorNx + dy * mirrorNy; // = dy
float rdx = 2.0f * dot * mirrorNx - dx; // = -dx
@@ -947,7 +947,7 @@ void ArcadePuzzle::wallAndPaddleCollision(int &ballLeft, int &ballTop, int &ball
}
}
-// ---- segmentsCross (FUN_00450120 / FUN_0045022b) ----------------------------
+// ---- segmentsCross ----------------------------------------------------------
// Returns true if segment P1->P2 crosses segment P3->P4 (including touching).
// Uses the standard 2D cross-product orientation test.
@@ -968,10 +968,10 @@ static bool segmentsCross(int p1x, int p1y, int p2x, int p2y,
return true;
}
-// ---- brickCollision (FUN_0044cad5) ------------------------------------------
+// ---- brickCollision ---------------------------------------------------------
// Ball center is inside brick area. Find which brick is hit and set collision type.
// Direct grid-index lookup (O(1)), neighbor-based face detection with line-segment
-// intersection test (FUN_00450120).
+// intersection test.
bool ArcadePuzzle::brickCollision(int &ballLeft, int &ballTop, int &ballRight, int &ballBottom,
int &ballCenterX, int &ballCenterY) {
diff --git a/engines/nancy/action/puzzle/arcadepuzzle.h b/engines/nancy/action/puzzle/arcadepuzzle.h
index b9b29b5ae20..2660aa98d81 100644
--- a/engines/nancy/action/puzzle/arcadepuzzle.h
+++ b/engines/nancy/action/puzzle/arcadepuzzle.h
@@ -67,26 +67,25 @@ protected:
bool pendingExplosion = false; // true while queued in explosion list (not yet erased)
};
- // ---------- Subfunction translations ----------
- void paddleMovement(); // FUN_0044b99b
- void ballAndCollision(); // FUN_0044bca3
- void processExplosions(); // FUN_0044f9dd
- void updateTimer(); // FUN_00450276
- void updateScore(); // FUN_00450354
- int getNextLevel() const; // FUN_0044e1a8
- void initSublevel(); // FUN_0044d3c7
- void resetRound(); // FUN_0044b78e
+ void paddleMovement();
+ void ballAndCollision();
+ void processExplosions();
+ void updateTimer();
+ void updateScore();
+ int getNextLevel() const;
+ void initSublevel();
+ void resetRound();
void wallAndPaddleCollision(int &ballLeft, int &ballTop, int &ballRight, int &ballBottom,
- int &ballCenterX, int &ballCenterY); // FUN_0044c378
+ int &ballCenterX, int &ballCenterY);
bool brickCollision(int &ballLeft, int &ballTop, int &ballRight, int &ballBottom,
- int &ballCenterX, int &ballCenterY); // FUN_0044cad5
- void applyCollision(); // FUN_0044ced2
+ int &ballCenterX, int &ballCenterY);
+ void applyCollision();
bool ballExited(int ballLeft, int ballTop, int ballRight, int ballBottom,
- int &ballCenterX, int &ballCenterY); // FUN_0044d20e
- void playBrickHitSound(); // FUN_0044fff5
+ int &ballCenterX, int &ballCenterY);
+ void playBrickHitSound();
void addToExplosionList(int brickIdx, uint32 delay);
- void generateBricks(); // FUN_0044e0b5
- void buildAngleTable(); // FUN_0044e1a8
+ void generateBricks();
+ void buildAngleTable();
void drawBrick(int idx);
void eraseBrick(int idx);
void drawPaddle();
diff --git a/engines/nancy/action/puzzle/matchpuzzle.h b/engines/nancy/action/puzzle/matchpuzzle.h
index 1218a30d63d..60f36a45d0b 100644
--- a/engines/nancy/action/puzzle/matchpuzzle.h
+++ b/engines/nancy/action/puzzle/matchpuzzle.h
@@ -65,18 +65,18 @@ protected:
// ---------- Helpers ----------
- // Shuffle: if allCells=true all cells, otherwise only (col,row). FUN_0046421e
+ // Shuffle: if allCells=true all cells, otherwise only (col,row).
void shuffleGrid(bool allCells, int col = 0, int row = 0);
- // Check cell (col,row) for a 3+ run; fills _match* fields. FUN_00464ba6
+ // Check cell (col,row) for a 3+ run; fills _match* fields.
void checkForMatch(int col, int row);
- // Compute the viewport-relative dest rect for cell (col,row). FUN_004643ef
+ // Compute the viewport-relative dest rect for cell (col,row).
void computeDestRect(int col, int row);
// Rendering helpers
void drawCell(int col, int row);
void eraseCell(int col, int row);
void redrawAllCells();
- void drawScorePanel(); // FUN_004660ff
+ void drawScorePanel();
// ---------- Data (read from stream) ----------
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.cpp b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
index 312dbb5bab1..70a641b3b81 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.cpp
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.cpp
@@ -588,9 +588,7 @@ void OneBuildPuzzle::checkAllPlaced() {
}
// Puzzles with a post-placement animation (e.g. scene 3637's music-box
- // crank) require the player to click _animRectA before the puzzle solves;
- // the original signals this in state 1 by skipping FUN_0047fadd when
- // +0xc27 (anim B non-empty) is set.
+ // crank) require the player to click _animRectA before the puzzle solves.
if (_hasFinalAnim && !_finalAnimDone) {
_waitingForFinalAnim = true;
return;
diff --git a/engines/nancy/action/puzzle/onebuildpuzzle.h b/engines/nancy/action/puzzle/onebuildpuzzle.h
index 7a26fb2d671..1339cf87c88 100644
--- a/engines/nancy/action/puzzle/onebuildpuzzle.h
+++ b/engines/nancy/action/puzzle/onebuildpuzzle.h
@@ -183,19 +183,19 @@ protected:
// --- Internal methods ---
- void playPickupSound(); // FUN_0047239c
- void playRotateSoundAndStartTimer(); // FUN_0047212b
- void playDropSound(); // FUN_004721dc
- void playGoodPlacementSound(); // FUN_00472792
- void playBadPlacementSound(); // FUN_00472440
- void checkAllPlaced(); // FUN_00472ac6
- void rotatePiece(int pieceIdx); // FUN_004719a5
- void updateDragPosition(Common::Point mouseVP); // FUN_00471490
+ void playPickupSound();
+ void playRotateSoundAndStartTimer();
+ void playDropSound();
+ void playGoodPlacementSound();
+ void playBadPlacementSound();
+ void checkAllPlaced();
+ void rotatePiece(int pieceIdx);
+ void updateDragPosition(Common::Point mouseVP);
// Update the render object for a piece (set _drawSurface and moveTo gameRect)
void updatePieceRender(int pieceIdx);
// Rotate a surface 90 degrees clockwise into dst (dst is allocated here)
static void rotateSurface90CW(const Graphics::ManagedSurface &src, Graphics::ManagedSurface &dst);
- // Clamp rect to viewport bounds while preserving dimensions - FUN_004713b8
+ // Clamp rect to viewport bounds while preserving dimensions
void clampRectToViewport(Common::Rect &rect);
// Final-animation helpers.
Commit: a583725952474d47ee3a435172d85e3e7b33bc25
https://github.com/scummvm/scummvm/commit/a583725952474d47ee3a435172d85e3e7b33bc25
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:11+03:00
Commit Message:
NANCY: Proper implementation of the textbox used in Nancy10+
Now, the text box in Nancy 10 is properly shown with its mini and full
mode.
The existing Textbox class is now used for the interface used in Nancy9
and earlier games. For the new interface introduced in Nancy10, the new
ScrollTextBox is used.
Changed paths:
A engines/nancy/ui/scrolltextbox.cpp
A engines/nancy/ui/scrolltextbox.h
engines/nancy/module.mk
engines/nancy/ui/textbox.cpp
engines/nancy/ui/textbox.h
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 0d37c38de54..4dfa6421f91 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -71,6 +71,7 @@ MODULE_OBJS = \
ui/notebookpopup.o \
ui/ornaments.o \
ui/scrollbar.o \
+ ui/scrolltextbox.o \
ui/taskbar.o \
ui/textbox.o \
ui/viewport.o \
diff --git a/engines/nancy/ui/scrolltextbox.cpp b/engines/nancy/ui/scrolltextbox.cpp
new file mode 100644
index 00000000000..c3b560b9282
--- /dev/null
+++ b/engines/nancy/ui/scrolltextbox.cpp
@@ -0,0 +1,350 @@
+/* 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/cursor.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/resource.h"
+
+#include "engines/nancy/state/scene.h"
+
+#include "engines/nancy/ui/scrolltextbox.h"
+
+namespace Nancy {
+namespace UI {
+
+static const uint16 kHypertextSurfaceHeight = 4096;
+
+ScrollTextBox::ScrollTextBox() :
+ RenderObject(10), // z=10: the textbox layer, above viewport (6) and taskbar (7).
+ _header(nullptr),
+ _tboxData(nullptr),
+ _scrollPos(0.0f),
+ _scrollbarDragging(false),
+ _scrollbarHovered(false),
+ _scrollbarGrabOffset(0),
+ _expanded(false),
+ _fontIDOverride(-1),
+ _autoClearTime(0) {}
+
+void ScrollTextBox::init() {
+ _tboxData = GetEngineData(TBOX);
+ assert(_tboxData);
+
+ // The popup chrome and the text viewport come from different chunks
+ // depending on the engine version, but both expose the same UIPopupHeader.
+ if (g_nancy->getGameType() >= kGameTypeNancy11) {
+ auto *sctb = GetEngineData(SCTB);
+ assert(sctb);
+ _header = &sctb->header;
+ _textViewportScreen = sctb->restoreSrcRect;
+ } else {
+ auto *uico = GetEngineData(UICO);
+ assert(uico);
+ _header = &uico->header;
+ _textViewportScreen = uico->textRect;
+ }
+
+ g_nancy->_resource->loadImage(_header->imageName, _overlayImage);
+
+ _fullPopupRect = _header->normalDestRect;
+ if (_header->overlayInGameFrame) {
+ const VIEW *view = GetEngineData(VIEW);
+ if (view) {
+ _fullPopupRect.translate(view->screenPosition.left, view->screenPosition.top);
+ }
+ }
+ moveTo(_fullPopupRect);
+
+ Common::Rect bounds = _screenPosition;
+ bounds.moveTo(0, 0);
+ _drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getInputPixelFormat());
+
+ // Key the text surface on the transparent color so only glyphs composite,
+ // over the chrome when expanded or onto the taskbar strip when mini.
+ const uint32 transColor = g_nancy->_graphics->getTransColor();
+ initSurfaces(textViewportLocal().width(), kHypertextSurfaceHeight,
+ g_nancy->_graphics->getInputPixelFormat(), transColor, transColor);
+ _fullSurface.setTransparentColor(transColor);
+
+ setVisible(false);
+
+ RenderObject::init();
+}
+
+void ScrollTextBox::registerGraphics() {
+ RenderObject::registerGraphics();
+}
+
+void ScrollTextBox::updateGraphics() {
+ if (_autoClearTime && g_nancy->getTotalPlayTime() > _autoClearTime) {
+ clear();
+ }
+
+ RenderObject::updateGraphics();
+}
+
+Common::Rect ScrollTextBox::textViewportLocal() const {
+ // The viewport is in screen space; the overlay shares it, so subtract the
+ // dest origin to get popup-local coordinates.
+ Common::Rect r = _textViewportScreen;
+ r.translate(-_header->normalDestRect.left,
+ -_header->normalDestRect.top);
+ return r;
+}
+
+void ScrollTextBox::drawBackground() {
+ _drawSurface.blitFrom(_overlayImage, _header->normalSrcRect,
+ Common::Point(0, 0));
+ _needsRedraw = true;
+}
+
+void ScrollTextBox::drawContent() {
+ // Render all text into the scratch surface to measure its height.
+ const uint32 transColor = g_nancy->_graphics->getTransColor();
+ _drawnTextHeight = 0;
+ _numDrawnLines = 0;
+ _hotspots.clear();
+ _fullSurface.fillRect(Common::Rect(0, 0, _fullSurface.w, _fullSurface.h), transColor);
+ _textHighlightSurface.fillRect(Common::Rect(0, 0, _textHighlightSurface.w, _textHighlightSurface.h), transColor);
+
+ Common::Rect textBounds(0, 0, _fullSurface.w, _fullSurface.h);
+ textBounds.top += _tboxData->scrollbarDefaultPos.y;
+ textBounds.left += _tboxData->scrollbarDefaultPos.x;
+
+ const uint16 fontID = _fontIDOverride != -1 ? (uint16)_fontIDOverride : _tboxData->conversationFontID;
+ drawAllText(textBounds, 0, fontID, _tboxData->highlightConversationFontID);
+
+ // Two discrete states: "mini" (<= 2 lines) is a short strip above the
+ // taskbar with no scrollbar; "expanded" (> 2 lines) is the full overlay
+ // covering the taskbar, with a scrollbar.
+ const Common::Rect viewport = textViewportLocal();
+ const int fullHeight = _fullPopupRect.height();
+ const int contentHeight = getInnerHeight();
+
+ _expanded = _numDrawnLines > 2;
+
+ Common::Rect boxRect;
+ int visibleTextHeight;
+ int scrollY = 0;
+
+ if (_expanded) {
+ boxRect = _fullPopupRect;
+ visibleTextHeight = viewport.height();
+ if (contentHeight > visibleTextHeight) {
+ scrollY = (int)(_scrollPos * (contentHeight - visibleTextHeight));
+ }
+ } else {
+ // Fixed two-line strip at the top of the overlay. Drop the chrome's
+ // bottom margin so it's just tall enough for two lines.
+ const Font *font = g_nancy->_graphics->getFont(fontID);
+ const int lineStep = font->getFontHeight() + font->getFontHeight() / 9;
+ const int twoLineContent = _tboxData->scrollbarDefaultPos.y + 2 * lineStep;
+ const int miniHeight = MIN(viewport.top + MAX(contentHeight, twoLineContent), fullHeight);
+ boxRect = Common::Rect(_fullPopupRect.left, _fullPopupRect.top,
+ _fullPopupRect.right, _fullPopupRect.top + miniHeight);
+ visibleTextHeight = contentHeight;
+ }
+
+ moveTo(boxRect);
+ _drawSurface.create(boxRect.width(), boxRect.height(), g_nancy->_graphics->getInputPixelFormat());
+
+ int textLeft = viewport.left;
+ if (_expanded) {
+ setTransparent(false);
+ drawBackground();
+ } else {
+ // The taskbar draws the strip, so keep mini transparent with no chrome,
+ // and pull the text left into the unused scrollbar gap.
+ setTransparent(true);
+ _drawSurface.clear(transColor);
+ const UISliderRecord &sl = _header->slider;
+ if (_header->sliderEnabled && !sl.destRect.isEmpty()) {
+ textLeft = toPopupLocal(sl.destRect, sl.destUsesGameFrameOffset != 0).left;
+ }
+ }
+
+ _drawSurface.blitFrom(_fullSurface,
+ Common::Rect(0, scrollY, _fullSurface.w, scrollY + visibleTextHeight),
+ Common::Point(textLeft, viewport.top));
+
+ if (_expanded) {
+ drawScrollbar(_scrollbarDragging ? 2 : (_scrollbarHovered ? 1 : 0));
+ }
+
+ _needsRedraw = true;
+}
+
+uint16 ScrollTextBox::getInnerHeight() const {
+ return _drawnTextHeight + _tboxData->scrollbarDefaultPos.y;
+}
+
+Common::Rect ScrollTextBox::toPopupLocal(const Common::Rect &chunkRect, bool useGameFrame) const {
+ Common::Rect r = chunkRect;
+ if (useGameFrame) {
+ const VIEW *view = GetEngineData(VIEW);
+ if (view) {
+ r.translate(view->screenPosition.left, view->screenPosition.top);
+ }
+ }
+ r.translate(-_screenPosition.left, -_screenPosition.top);
+ return r;
+}
+
+Common::Point ScrollTextBox::popupLocalMouse(const Common::Point &screenMouse) const {
+ return Common::Point(screenMouse.x - _screenPosition.left,
+ screenMouse.y - _screenPosition.top);
+}
+
+Common::Rect ScrollTextBox::computeThumbRect() const {
+ const UISliderRecord &sl = _header->slider;
+ if (!_header->sliderEnabled || sl.destRect.isEmpty() || sl.sourceRects[0].isEmpty()) {
+ return Common::Rect();
+ }
+
+ const int trackHeight = sl.destRect.height();
+ const int thumbHeight = sl.sourceRects[0].height();
+ const int travel = MAX(0, trackHeight - thumbHeight);
+ const int thumbY = sl.destRect.top + (int)(_scrollPos * travel);
+
+ Common::Rect chunkThumb(sl.destRect.left, thumbY,
+ sl.destRect.left + sl.sourceRects[0].width(),
+ thumbY + thumbHeight);
+ return toPopupLocal(chunkThumb, sl.destUsesGameFrameOffset != 0);
+}
+
+void ScrollTextBox::drawScrollbar(uint state) {
+ const UISliderRecord &sl = _header->slider;
+ if (!_header->sliderEnabled) {
+ return;
+ }
+
+ const Common::Rect thumb = computeThumbRect();
+ if (thumb.isEmpty()) {
+ return;
+ }
+
+ _drawSurface.blitFrom(_overlayImage, sl.sourceRects[state],
+ Common::Point(thumb.left, thumb.top));
+}
+
+void ScrollTextBox::addTextLine(const Common::String &text, uint32 autoClearTime) {
+ HypertextParser::addTextLine(text);
+
+ if (autoClearTime != 0) {
+ _autoClearTime = g_nancy->getTotalPlayTime() + autoClearTime;
+ }
+
+ drawContent();
+ setVisible(true);
+}
+
+void ScrollTextBox::setOverrideFont(uint fontID) {
+ auto *bsum = GetEngineData(BSUM);
+ assert(bsum);
+
+ if (fontID >= bsum->numFonts) {
+ error("Requested invalid override font ID %u in ScrollTextBox", fontID);
+ }
+
+ _fontIDOverride = (int)fontID;
+}
+
+void ScrollTextBox::clear() {
+ HypertextParser::clear();
+
+ _scrollPos = 0.0f;
+ _scrollbarDragging = false;
+ _scrollbarHovered = false;
+ _expanded = false;
+ _fontIDOverride = -1;
+ _autoClearTime = 0;
+ _drawnTextHeight = 0;
+
+ const uint32 transColor = g_nancy->_graphics->getTransColor();
+ _fullSurface.fillRect(Common::Rect(0, 0, _fullSurface.w, _fullSurface.h), transColor);
+ _textHighlightSurface.fillRect(Common::Rect(0, 0, _textHighlightSurface.w, _textHighlightSurface.h), transColor);
+
+ setVisible(false);
+}
+
+void ScrollTextBox::handleInput(NancyInput &input) {
+ if (!_isVisible) {
+ return;
+ }
+
+ const Common::Point localMouse = popupLocalMouse(input.mousePos);
+ const UISliderRecord &slider = _header->slider;
+
+ // The scrollbar only exists in the expanded state.
+ if (_header->sliderEnabled && _expanded) {
+ const Common::Rect trackLocal = toPopupLocal(slider.destRect, slider.destUsesGameFrameOffset != 0);
+ const int trackHeight = trackLocal.height();
+ const int thumbHeight = slider.sourceRects[0].height();
+ const int travel = MAX(0, trackHeight - thumbHeight);
+ const int thumbY = trackLocal.top + (int)(_scrollPos * travel);
+ Common::Rect thumbLocal(trackLocal.left, thumbY,
+ trackLocal.left + slider.sourceRects[0].width(),
+ thumbY + thumbHeight);
+ const bool overThumb = thumbLocal.contains(localMouse);
+
+ if (_scrollbarDragging) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+
+ const int newThumbTop = localMouse.y - _scrollbarGrabOffset;
+ const int clamped = CLIP<int>(newThumbTop, trackLocal.top, trackLocal.top + travel);
+ _scrollPos = travel > 0 ? (float)(clamped - trackLocal.top) / (float)travel : 0.0f;
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _scrollbarDragging = false;
+ }
+
+ drawContent();
+ input.eatMouseInput();
+ return;
+ }
+
+ if (overThumb != _scrollbarHovered) {
+ _scrollbarHovered = overThumb;
+ drawScrollbar(overThumb ? 1 : 0);
+ _needsRedraw = true;
+ }
+
+ if (overThumb) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+ if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
+ _scrollbarDragging = true;
+ _scrollbarGrabOffset = localMouse.y - thumbY;
+ drawScrollbar(2);
+ _needsRedraw = true;
+ input.eatMouseInput();
+ return;
+ }
+ }
+ }
+
+ // Don't swallow clicks over the whole rect: the box overlaps the taskbar and
+ // captions can persist, so only the scrollbar consumes input.
+}
+
+} // End of namespace UI
+} // End of namespace Nancy
diff --git a/engines/nancy/ui/scrolltextbox.h b/engines/nancy/ui/scrolltextbox.h
new file mode 100644
index 00000000000..4ab89e276da
--- /dev/null
+++ b/engines/nancy/ui/scrolltextbox.h
@@ -0,0 +1,104 @@
+/* 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_UI_SCROLLTEXTBOX_H
+#define NANCY_UI_SCROLLTEXTBOX_H
+
+#include "engines/nancy/misc/hypertext.h"
+#include "engines/nancy/renderobject.h"
+
+namespace Nancy {
+
+struct NancyInput;
+struct UIPopupHeader;
+struct TBOX;
+
+namespace UI {
+
+// Nancy 10+ game text box: a UIPopupHeader overlay popup at the bottom of the
+// screen for narration and inventory/timer captions. The header comes from UICO
+// (Nancy 10) or SCTB (Nancy 11), which are structurally identical, so one widget
+// serves both. Shows caption text only (no response hotspots; conversation
+// dialogue uses ConversationPopup). The legacy Textbox forwards to it for
+// Nancy 10+.
+class ScrollTextBox : public RenderObject, public Misc::HypertextParser {
+public:
+ ScrollTextBox();
+ ~ScrollTextBox() override = default;
+
+ void init() override;
+ void registerGraphics() override;
+ void updateGraphics() override;
+ void handleInput(NancyInput &input);
+
+ // Append a line of text. The popup is shown on the first non-empty
+ // addition. A non-zero autoClearTime schedules an automatic clear() after
+ // that many milliseconds (used by inventory closed captions).
+ void addTextLine(const Common::String &text, uint32 autoClearTime = 0);
+ void setOverrideFont(uint fontID);
+ void clear() override;
+
+private:
+ void drawBackground();
+ void drawContent();
+ void drawScrollbar(uint state);
+ uint16 getInnerHeight() const;
+
+ // The text content area in popup-local coordinates (the screen-space text
+ // viewport brought into the popup surface's space).
+ Common::Rect textViewportLocal() const;
+
+ // Popup-local bounding rect of the scrollbar thumb at the current scroll
+ // position, or an empty rect when the slider is disabled.
+ Common::Rect computeThumbRect() const;
+
+ Common::Rect toPopupLocal(const Common::Rect &chunkRect, bool useGameFrame) const;
+ Common::Point popupLocalMouse(const Common::Point &screenMouse) const;
+
+ // Popup chrome (overlay image, dest rects, slider) supplied by the
+ // version-specific chunk: UICO in Nancy 10, SCTB in Nancy 11.
+ const UIPopupHeader *_header;
+ // Text content area, in screen coordinates.
+ Common::Rect _textViewportScreen;
+ // The fully-expanded popup rect on screen, with the game-frame offset
+ // applied. The mini strip shares its top edge.
+ Common::Rect _fullPopupRect;
+ const TBOX *_tboxData;
+
+ Graphics::ManagedSurface _overlayImage;
+
+ float _scrollPos;
+ bool _scrollbarDragging;
+ bool _scrollbarHovered;
+ int _scrollbarGrabOffset;
+
+ // True in the expanded state (> 2 lines): full overlay with scrollbar.
+ // False in the mini state (<= 2 lines): strip above the taskbar, no scrollbar.
+ bool _expanded;
+
+ int _fontIDOverride;
+ uint32 _autoClearTime;
+};
+
+} // End of namespace UI
+} // End of namespace Nancy
+
+#endif // NANCY_UI_SCROLLTEXTBOX_H
diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index 270f3c0d3f9..e6a2adcc206 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -31,6 +31,7 @@
#include "engines/nancy/ui/textbox.h"
#include "engines/nancy/ui/scrollbar.h"
+#include "engines/nancy/ui/scrolltextbox.h"
namespace Nancy {
namespace UI {
@@ -42,59 +43,26 @@ Textbox::Textbox() :
_highlightRObj(g_nancy->getGameType() >= kGameTypeNancy10 ? 11 : 7),
_fontIDOverride(-1),
_autoClearTime(0),
- _isFullMode(false),
- _fullModeCloseTime(0) {}
+ _scrollTextBox(nullptr) {}
Textbox::~Textbox() {
delete _scrollbar;
+ delete _scrollTextBox;
}
void Textbox::init() {
- auto *tbox = GetEngineData(TBOX);
- assert(tbox);
-
+ // Nancy 10+: the game text box is a UIPopupHeader overlay popup (UICO in
+ // Nancy 10, SCTB in Nancy 11). Delegate to a ScrollTextBox; the code below
+ // is the Nancy 1-9 bottom-right flat box.
if (g_nancy->getGameType() >= kGameTypeNancy10) {
- auto *bsum = GetEngineData(BSUM);
- assert(bsum);
-
- // The bsum rect spans the full taskbar strip (open mode); the
- // closed mode clips at the taskbar's top edge so the buttons stay
- // visible. We keep both rects so setFullMode() can toggle between
- // them without reallocating.
- _openRect = bsum->textboxScreenPosition;
- _closedRect = _openRect;
- const TASK *taskData = GetEngineData(TASK);
- if (taskData && taskData->dstRect.top > _closedRect.top &&
- taskData->dstRect.top < _closedRect.bottom) {
- _closedRect.bottom = taskData->dstRect.top;
- }
-
- // Size the backing surface to the OPEN rect so toggling never
- // reallocates. The visible slice is constrained via _drawSurface.
- initSurfaces(_openRect.width(), _openRect.height(),
- g_nancy->_graphics->getScreenPixelFormat(),
- tbox->textBackground, tbox->highlightTextBackground);
-
- _isFullMode = false;
- applyDisplayMode();
-
- RenderObject::init();
-
- // Nancy 11+ scrollbar for the OPEN-mode text overflow. Nancy 10
- // has no scrollbar in either state; left intentionally nullptr.
- if (g_nancy->getGameType() >= kGameTypeNancy11
- && tbox->scrollbarSrcBounds.width() > 0) {
- _scrollbar = new Scrollbar(11,
- tbox->scrollbarSrcBounds,
- tbox->scrollbarDefaultPos,
- tbox->scrollbarMaxScroll - tbox->scrollbarDefaultPos.y);
- _scrollbar->init();
- }
-
- setVisible(false);
+ _scrollTextBox = new ScrollTextBox();
+ _scrollTextBox->init();
return;
}
+ auto *tbox = GetEngineData(TBOX);
+ assert(tbox);
+
auto *bsum = GetEngineData(BSUM);
assert(bsum);
@@ -118,8 +86,8 @@ void Textbox::init() {
}
void Textbox::registerGraphics() {
- if (g_nancy->getGameType() >= kGameTypeNancy11) {
- // TODO: Textbox for Nancy11+
+ if (_scrollTextBox) {
+ _scrollTextBox->registerGraphics();
return;
}
@@ -131,16 +99,14 @@ void Textbox::registerGraphics() {
}
void Textbox::updateGraphics() {
+ if (_scrollTextBox) {
+ // The ScrollTextBox is registered separately and updates itself.
+ return;
+ }
+
if (_autoClearTime && g_nancy->getTotalPlayTime() > _autoClearTime)
clear();
- // Nancy 10+ open-mode auto-close timer (mirrors the DAT_005a7a7d
- // check in case 0x4a of ProcessActionRecords).
- if (_isFullMode && _fullModeCloseTime &&
- g_nancy->getTotalPlayTime() > _fullModeCloseTime) {
- setFullMode(false);
- }
-
if (_needsTextRedraw)
drawTextbox();
@@ -153,49 +119,17 @@ void Textbox::updateGraphics() {
RenderObject::updateGraphics();
}
-void Textbox::applyDisplayMode() {
- if (g_nancy->getGameType() < kGameTypeNancy10) {
- return;
- }
-
- const Common::Rect &target = _isFullMode ? _openRect : _closedRect;
- moveTo(target);
- _highlightRObj.moveTo(target);
-
- Common::Rect outerBoundingBox = target;
- outerBoundingBox.moveTo(0, 0);
- _drawSurface.create(_fullSurface, outerBoundingBox);
-
- // Nancy 11+ scrollbar only makes sense in open mode; hide it in
- // closed mode regardless of game version.
- if (_scrollbar) {
- _scrollbar->setVisible(_isFullMode);
- }
-
- _needsTextRedraw = true;
- _needsRedraw = true;
+void Textbox::setFullMode(bool open, uint32 timeoutMs) {
+ // No-op: the Nancy 10+ overlay shows itself when it holds text, and the
+ // Nancy 1-9 box has no full mode. Kept for the FrameTextBox AR's API.
}
-void Textbox::setFullMode(bool open, uint32 timeoutMs) {
- if (g_nancy->getGameType() < kGameTypeNancy10) {
- return;
- }
- if (_isFullMode == open) {
- // Re-arm the timer on a repeat open call.
- if (open && timeoutMs) {
- _fullModeCloseTime = g_nancy->getTotalPlayTime() + timeoutMs;
- }
+void Textbox::handleInput(NancyInput &input) {
+ if (_scrollTextBox) {
+ _scrollTextBox->handleInput(input);
return;
}
- _isFullMode = open;
- _fullModeCloseTime = (open && timeoutMs)
- ? g_nancy->getTotalPlayTime() + timeoutMs
- : 0;
- applyDisplayMode();
-}
-
-void Textbox::handleInput(NancyInput &input) {
if (!isVisible())
return;
@@ -239,33 +173,35 @@ void Textbox::drawTextbox() {
assert(tbox);
Common::Rect textBounds = _fullSurface.getBounds();
- uint16 baseFontID;
uint16 highlightFontID = tbox->highlightConversationFontID;
- if (g_nancy->getGameType() >= kGameTypeNancy10) {
- baseFontID = (_fontIDOverride != -1) ? _fontIDOverride : tbox->conversationFontID;
- } else {
- // TODO: These bounds are not right: the right offset is a bit off,
- // and the left offset takes into account the scrollbar, which doesn't
- // exist in this widget.
- textBounds.top += tbox->upOffset;
- textBounds.bottom -= tbox->downOffset;
- textBounds.left += tbox->leftOffset;
- textBounds.right -= tbox->rightOffset;
-
- baseFontID = (_fontIDOverride != -1) ? _fontIDOverride : tbox->defaultFontID;
-
- const Font *font = g_nancy->_graphics->getFont(baseFontID);
- textBounds.top -= font->getFontHeight();
- }
+ // TODO: These bounds are not right: the right offset is a bit off,
+ // and the left offset takes into account the scrollbar, which doesn't
+ // exist in this widget.
+ textBounds.top += tbox->upOffset;
+ textBounds.bottom -= tbox->downOffset;
+ textBounds.left += tbox->leftOffset;
+ textBounds.right -= tbox->rightOffset;
+
+ uint16 baseFontID = (_fontIDOverride != -1) ? _fontIDOverride : tbox->defaultFontID;
+
+ const Font *font = g_nancy->_graphics->getFont(baseFontID);
+ textBounds.top -= font->getFontHeight();
HypertextParser::drawAllText(textBounds, 0, baseFontID, highlightFontID);
- setVisible(g_nancy->getGameType() <= kGameTypeNancy9 || _isFullMode);
+ // The Nancy 1-9 box is always on screen. (Nancy 10+ renders through the
+ // ScrollTextBox and never reaches this path.)
+ setVisible(true);
}
void Textbox::clear() {
- if (!_textLines.empty() || g_nancy->getGameType() >= kGameTypeNancy10) {
+ if (_scrollTextBox) {
+ _scrollTextBox->clear();
+ return;
+ }
+
+ if (!_textLines.empty()) {
HypertextParser::clear();
if (_scrollbar) {
_scrollbar->resetPosition();
@@ -274,17 +210,6 @@ void Textbox::clear() {
_fontIDOverride = -1;
_needsRedraw = true;
_autoClearTime = 0;
-
- // Nancy 10+: the text strip overlaps the taskbar buttons, so
- // hide it whenever it has no content to show. Also drop the
- // open-mode overlay so the next entry starts fresh in closed
- // mode unless a kVariant74 AR re-opens it.
- if (g_nancy->getGameType() >= kGameTypeNancy10) {
- if (_isFullMode) {
- setFullMode(false);
- }
- setVisible(false);
- }
}
}
@@ -297,6 +222,11 @@ void Textbox::addTextLine(const Common::String &text, uint32 autoClearTime) {
if (text.contains(debugString))
return;
+ if (_scrollTextBox) {
+ _scrollTextBox->addTextLine(text, autoClearTime);
+ return;
+ }
+
HypertextParser::addTextLine(text);
if (autoClearTime != 0) {
@@ -312,6 +242,11 @@ void Textbox::addTextLine(const Common::String &text, uint32 autoClearTime) {
}
void Textbox::setOverrideFont(const uint fontID) {
+ if (_scrollTextBox) {
+ _scrollTextBox->setOverrideFont(fontID);
+ return;
+ }
+
auto *bsum = GetEngineData(BSUM);
assert(bsum);
diff --git a/engines/nancy/ui/textbox.h b/engines/nancy/ui/textbox.h
index c4db8b68f12..9fbc7266139 100644
--- a/engines/nancy/ui/textbox.h
+++ b/engines/nancy/ui/textbox.h
@@ -34,6 +34,7 @@ struct NancyInput;
namespace UI {
class Scrollbar;
+class ScrollTextBox;
class Textbox : public RenderObject, public Misc::HypertextParser {
public:
@@ -51,38 +52,25 @@ public:
void addTextLine(const Common::String &text, uint32 autoClearTime = 0);
void setOverrideFont(const uint fontID);
- // Nancy 10+ open/full mode. When `open` is true the textbox extends
- // down to cover the taskbar buttons; `timeoutMs` schedules an automatic
- // return to closed mode Passing 0 disables the auto-close (the caller is
- // then responsible for closing). No-op for pre-Nancy 10 games.
+ // Vestigial "full mode" toggle, now a no-op. Kept for the FrameTextBox AR.
void setFullMode(bool open, uint32 timeoutMs = 15000);
- bool isFullMode() const { return _isFullMode; }
+ bool isFullMode() const { return false; }
private:
- enum DisplayMode {
- kModeClosed = 0,
- kModeOpen = 1
- };
-
uint16 getInnerHeight() const;
void onScrollbarMove();
- void applyDisplayMode();
RenderObject _highlightRObj;
Scrollbar *_scrollbar;
+ // Nancy 10+ overlay popup the public API forwards to (UICO/SCTB-driven).
+ // The legacy members above are unused in that mode.
+ ScrollTextBox *_scrollTextBox;
+
float _scrollbarPos;
uint32 _autoClearTime;
int _fontIDOverride;
-
- // Nancy 10+ open/closed strip geometry. `_closedRect` is the small
- // taskbar-clipped strip; `_openRect` is the full bsum textbox area
- // (extends through the taskbar zone).
- bool _isFullMode;
- uint32 _fullModeCloseTime;
- Common::Rect _closedRect;
- Common::Rect _openRect;
};
} // End of namespace UI
Commit: d7154472390a4f05b3a1b61da23e8ba9b18cce58
https://github.com/scummvm/scummvm/commit/d7154472390a4f05b3a1b61da23e8ba9b18cce58
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:12+03:00
Commit Message:
NANCY: More comments cleanup
Changed paths:
engines/nancy/ui/taskbar.cpp
diff --git a/engines/nancy/ui/taskbar.cpp b/engines/nancy/ui/taskbar.cpp
index abd2cb1ce66..a7f70f473a2 100644
--- a/engines/nancy/ui/taskbar.cpp
+++ b/engines/nancy/ui/taskbar.cpp
@@ -114,7 +114,7 @@ Taskbar::ButtonState Taskbar::restingState(uint index) const {
return kButtonDisabled;
}
// Disable override (ControlUIItems) takes precedence over the badge â
- // FUN_004d51c3 only draws the notify sprite when state != 3.
+ // the notify sprite is only drawn when the button is not disabled.
const ButtonOverride &o = _overrides[index];
if (o.active && _currentScene >= o.startScene && _currentScene <= o.endScene) {
return kButtonDisabled;
Commit: 67e94fbbb494251a93c67cc0d7a828750720d2c7
https://github.com/scummvm/scummvm/commit/67e94fbbb494251a93c67cc0d7a828750720d2c7
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:16+03:00
Commit Message:
NANCY: Add an enum and unify states in the Nancy10+ popups
Changed paths:
engines/nancy/commontypes.h
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/cellphonepopup.h
engines/nancy/ui/conversationpopup.cpp
engines/nancy/ui/conversationpopup.h
engines/nancy/ui/inventorypopup.cpp
engines/nancy/ui/inventorypopup.h
engines/nancy/ui/notebookpopup.cpp
engines/nancy/ui/notebookpopup.h
engines/nancy/ui/scrolltextbox.cpp
engines/nancy/ui/scrolltextbox.h
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 9a55aaad7c7..18d361e844d 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -349,6 +349,16 @@ struct StaticData {
void readData(Common::SeekableReadStream &stream, Common::Language language, uint32 endPos, int8 majorVersion, int8 minorVersion);
};
+// Source-rect order shared by the close button and slider widgets in the
+// Nancy 10+ popup header. The tab/filter strips use a different order, with
+// the active entry at index 0.
+enum UIButtonState {
+ kUIButtonIdle = 0,
+ kUIButtonHover = 1,
+ kUIButtonPressed = 2, // dragging, for sliders
+ kUIButtonDisabled = 3
+};
+
// Reusable button widget embedded in Nancy 10+ popup UIs.
struct UIButtonRecord {
static const uint kRecordSize = 239;
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 53c3b2d74c8..2fbfda3301b 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -409,7 +409,7 @@ void CellPhonePopup::drawChrome() {
? _uiclData->fullEmptyScreenSrc
: _uiclData->header.normalSrcRect;
_drawSurface.blitFrom(_overlayImage, chromeSrc, Common::Point(0, 0));
- drawCloseButton(_closeButtonHovered ? 1 : 0);
+ drawCloseButton(_closeButtonHovered);
if (!isSubScreenState()) {
drawHelpButton(0);
}
@@ -647,14 +647,13 @@ void CellPhonePopup::drawHelpButton(uint state) {
hb.destRect.top - chunkOrigin.y));
}
-void CellPhonePopup::drawCloseButton(uint state) {
+void CellPhonePopup::drawCloseButton(bool hovered) {
const UIButtonRecord &btn = _uiclData->header.secondaryButton;
if (!_uiclData->header.secondaryButtonEnabled || btn.destRect.isEmpty()) {
return;
}
- const uint stateIdx = MIN<uint>(state, 3);
- Common::Rect src = btn.sourceRects[stateIdx];
+ Common::Rect src = btn.sourceRects[hovered ? kUIButtonHover : kUIButtonIdle];
if (src.isEmpty()) {
src = btn.sourceRects[0];
}
@@ -820,7 +819,7 @@ void CellPhonePopup::drawContentView() {
// Content view runs under the zoomed-in chrome (drawChrome blits
// fullEmptyScreenSrc for kContentView), so the keypad is no longer
// visible underneath and we can render into the larger LCD area
- // that emailListContainer / FUN_004dae28 define.
+ // that emailListContainer defines.
const Common::Rect &ws =
_uiclData->emailListContainer.isEmpty()
? _uiclData->welcomeScreen.destRect
@@ -1493,7 +1492,7 @@ void CellPhonePopup::handleInput(NancyInput &input) {
const bool overClose = closeScreen.contains(input.mousePos);
if (overClose != _closeButtonHovered) {
_closeButtonHovered = overClose;
- drawCloseButton(overClose ? 1 : 0);
+ drawCloseButton(overClose);
_needsRedraw = true;
}
if (overClose) {
diff --git a/engines/nancy/ui/cellphonepopup.h b/engines/nancy/ui/cellphonepopup.h
index 46a8517fc96..8ea47542274 100644
--- a/engines/nancy/ui/cellphonepopup.h
+++ b/engines/nancy/ui/cellphonepopup.h
@@ -105,7 +105,7 @@ private:
void drawConnectingSprite();
void drawDialedNumber();
void drawHelpButton(uint state);
- void drawCloseButton(uint state);
+ void drawCloseButton(bool hovered);
void drawStatusLabels();
void drawDirectoryList();
void drawDirectoryArrows();
diff --git a/engines/nancy/ui/conversationpopup.cpp b/engines/nancy/ui/conversationpopup.cpp
index 13a84a1a891..e4dd2892d15 100644
--- a/engines/nancy/ui/conversationpopup.cpp
+++ b/engines/nancy/ui/conversationpopup.cpp
@@ -94,7 +94,7 @@ void ConversationPopup::open() {
_textHighlightSurface.fillRect(Common::Rect(0, 0, _textHighlightSurface.w, _textHighlightSurface.h), 0);
drawBackground();
- drawScrollbar(0);
+ drawScrollbar(kUIButtonIdle);
setVisible(true);
}
@@ -103,7 +103,7 @@ void ConversationPopup::addTextLine(const Common::String &text) {
drawBackground();
drawContent();
- drawScrollbar(0);
+ drawScrollbar(kUIButtonIdle);
}
void ConversationPopup::close() {
@@ -190,7 +190,7 @@ Common::Rect ConversationPopup::computeThumbRect() const {
return toPopupLocal(chunkThumb, sl.destUsesGameFrameOffset != 0);
}
-void ConversationPopup::drawScrollbar(uint state) {
+void ConversationPopup::drawScrollbar(UIButtonState state) {
const UISliderRecord &sl = _uicoData->header.slider;
if (!_uicoData->header.sliderEnabled) {
return;
@@ -233,11 +233,11 @@ void ConversationPopup::handleInput(NancyInput &input) {
drawBackground();
drawContent();
- drawScrollbar(2);
+ drawScrollbar(kUIButtonPressed);
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
- drawScrollbar(overThumb ? 1 : 0);
+ drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
input.eatMouseInput();
@@ -246,7 +246,7 @@ void ConversationPopup::handleInput(NancyInput &input) {
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
- drawScrollbar(overThumb ? 1 : 0);
+ drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
@@ -255,7 +255,7 @@ void ConversationPopup::handleInput(NancyInput &input) {
if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
_scrollbarDragging = true;
_scrollbarGrabOffset = localMouse.y - thumbY;
- drawScrollbar(2);
+ drawScrollbar(kUIButtonPressed);
_needsRedraw = true;
input.eatMouseInput();
return;
diff --git a/engines/nancy/ui/conversationpopup.h b/engines/nancy/ui/conversationpopup.h
index 50cf6a0f22d..128c5b2af80 100644
--- a/engines/nancy/ui/conversationpopup.h
+++ b/engines/nancy/ui/conversationpopup.h
@@ -22,6 +22,7 @@
#ifndef NANCY_UI_CONVERSATIONPOPUP_H
#define NANCY_UI_CONVERSATIONPOPUP_H
+#include "engines/nancy/commontypes.h"
#include "engines/nancy/misc/hypertext.h"
#include "engines/nancy/renderobject.h"
@@ -64,7 +65,7 @@ public:
private:
void drawBackground();
void drawContent();
- void drawScrollbar(uint state);
+ void drawScrollbar(UIButtonState state);
uint16 getInnerHeight() const;
// Returns the popup-local bounding rect of the scrollbar thumb at
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 3ea138714fd..3b5286e51af 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -164,15 +164,9 @@ void InventoryPopup::drawBackground() {
Common::Rect src = _uiivData->header.normalSrcRect;
_drawSurface.blitFrom(_overlayImage, src, Common::Point(0, 0));
- drawCloseButton(_closeButtonHovered ? kStateHover : kStateIdle);
+ drawCloseButton(_closeButtonHovered);
- WidgetState sliderState = kStateIdle;
- if (_scrollbarDragging)
- sliderState = kStatePressed;
- else if (_scrollbarHovered)
- sliderState = kStateHover;
-
- drawScrollbar(sliderState);
+ drawScrollbar(_scrollbarDragging ? kUIButtonPressed : (_scrollbarHovered ? kUIButtonHover : kUIButtonIdle));
}
Common::Rect InventoryPopup::computeSliderRect() const {
@@ -195,7 +189,7 @@ Common::Rect InventoryPopup::computeSliderRect() const {
return r;
}
-void InventoryPopup::drawScrollbar(WidgetState state) {
+void InventoryPopup::drawScrollbar(UIButtonState state) {
const UISliderRecord &sl = _uiivData->header.slider;
if (!_uiivData->header.sliderEnabled)
return;
@@ -226,9 +220,9 @@ void InventoryPopup::updatePageFromScroll() {
_currentPage = MIN<uint>(page, maxPage);
}
-void InventoryPopup::drawCloseButton(WidgetState state) {
+void InventoryPopup::drawCloseButton(bool hovered) {
const UIButtonRecord &btn = _uiivData->header.secondaryButton;
- Common::Rect spr = btn.sourceRects[state];
+ Common::Rect spr = btn.sourceRects[hovered ? kUIButtonHover : kUIButtonIdle];
Common::Rect dstRect = btn.destRect;
if (btn.destUsesGameFrameOffset) {
const VIEW *view = GetEngineData(VIEW);
@@ -347,7 +341,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
- drawScrollbar(overScrollbar ? kStateHover : kStateIdle);
+ drawScrollbar(overScrollbar ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
input.eatMouseInput();
@@ -356,7 +350,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
if (overScrollbar != _scrollbarHovered) {
_scrollbarHovered = overScrollbar;
- drawScrollbar(overScrollbar ? kStateHover : kStateIdle);
+ drawScrollbar(overScrollbar ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
if (overScrollbar) {
@@ -364,7 +358,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
_scrollbarDragging = true;
_scrollbarGrabOffset = chunkMouse.y - thumbY;
- drawScrollbar(kStatePressed);
+ drawScrollbar(kUIButtonPressed);
_needsRedraw = true;
input.eatMouseInput();
return;
@@ -384,7 +378,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
const bool overClose = closeScreen.contains(input.mousePos);
if (overClose != _closeButtonHovered) {
_closeButtonHovered = overClose;
- drawCloseButton(overClose ? kStateHover : kStateIdle);
+ drawCloseButton(overClose);
_needsRedraw = true;
}
if (overClose) {
diff --git a/engines/nancy/ui/inventorypopup.h b/engines/nancy/ui/inventorypopup.h
index 97c7193c185..e3d065e5d92 100644
--- a/engines/nancy/ui/inventorypopup.h
+++ b/engines/nancy/ui/inventorypopup.h
@@ -22,6 +22,7 @@
#ifndef NANCY_UI_INVENTORYPOPUP_H
#define NANCY_UI_INVENTORYPOPUP_H
+#include "engines/nancy/commontypes.h"
#include "engines/nancy/renderobject.h"
namespace Nancy {
@@ -80,8 +81,8 @@ private:
void drawFilterTabs();
void drawFilterTab(uint index, bool drawHover = false);
void drawFilterCaption();
- void drawCloseButton(WidgetState state);
- void drawScrollbar(WidgetState state);
+ void drawCloseButton(bool hovered);
+ void drawScrollbar(UIButtonState state);
void rebuildVisibleList();
void setActiveFilterIndex(uint index);
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index 555fbba55ac..7895b960474 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -141,15 +141,9 @@ void NotebookPopup::drawBackground() {
}
void NotebookPopup::drawForeground() {
- drawCloseButton(_closeButtonHovered ? kStateHover : kStateIdle);
+ drawCloseButton(_closeButtonHovered);
- WidgetState sliderState = kStateIdle;
- if (_scrollbarDragging) {
- sliderState = kStatePressed;
- } else if (_scrollbarHovered) {
- sliderState = kStateHover;
- }
- drawScrollbar(sliderState);
+ drawScrollbar(_scrollbarDragging ? kUIButtonPressed : (_scrollbarHovered ? kUIButtonHover : kUIButtonIdle));
}
Common::Rect NotebookPopup::toPopupLocal(const Common::Rect &chunkRect, bool useGameFrame) const {
@@ -192,7 +186,7 @@ Common::Rect NotebookPopup::computeThumbRect() const {
return toPopupLocal(chunkThumb, sl.destUsesGameFrameOffset != 0);
}
-void NotebookPopup::drawScrollbar(WidgetState state) {
+void NotebookPopup::drawScrollbar(UIButtonState state) {
const UISliderRecord &sl = _uinbData->header.slider;
if (!_uinbData->header.sliderEnabled)
return;
@@ -205,12 +199,12 @@ void NotebookPopup::drawScrollbar(WidgetState state) {
_drawSurface.blitFrom(_overlayImage, spr, Common::Point(thumb.left, thumb.top));
}
-void NotebookPopup::drawCloseButton(WidgetState state) {
+void NotebookPopup::drawCloseButton(bool hovered) {
const UIButtonRecord &btn = _uinbData->header.secondaryButton;
if (!_uinbData->header.secondaryButtonEnabled || btn.destRect.isEmpty())
return;
- Common::Rect spr = btn.sourceRects[state];
+ Common::Rect spr = btn.sourceRects[hovered ? kUIButtonHover : kUIButtonIdle];
const Common::Rect dstLocal = toPopupLocal(btn.destRect, btn.destUsesGameFrameOffset != 0);
const Graphics::ManagedSurface &srcSurf = _closeButtonImage.w != 0 ? _closeButtonImage : _overlayImage;
@@ -277,7 +271,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
- drawScrollbar(overThumb ? kStateHover : kStateIdle);
+ drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
input.eatMouseInput();
@@ -286,7 +280,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
- drawScrollbar(overThumb ? kStateHover : kStateIdle);
+ drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
if (overThumb) {
@@ -294,7 +288,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
_scrollbarDragging = true;
_scrollbarGrabOffset = localMouse.y - thumbY;
- drawScrollbar(kStatePressed);
+ drawScrollbar(kUIButtonPressed);
_needsRedraw = true;
input.eatMouseInput();
return;
@@ -310,7 +304,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
const bool overClose = closeLocal.contains(localMouse);
if (overClose != _closeButtonHovered) {
_closeButtonHovered = overClose;
- drawCloseButton(overClose ? kStateHover : kStateIdle);
+ drawCloseButton(overClose);
_needsRedraw = true;
}
if (overClose) {
diff --git a/engines/nancy/ui/notebookpopup.h b/engines/nancy/ui/notebookpopup.h
index 922e5fb6ab5..e1eead411d2 100644
--- a/engines/nancy/ui/notebookpopup.h
+++ b/engines/nancy/ui/notebookpopup.h
@@ -22,6 +22,7 @@
#ifndef NANCY_UI_NOTEBOOKPOPUP_H
#define NANCY_UI_NOTEBOOKPOPUP_H
+#include "engines/nancy/commontypes.h"
#include "engines/nancy/renderobject.h"
#include "engines/nancy/misc/hypertext.h"
@@ -69,8 +70,8 @@ private:
// Paint foreground widgets (close button, scrollbar) on top of the
// already-drawn background + content layers.
void drawForeground();
- void drawCloseButton(WidgetState state);
- void drawScrollbar(WidgetState state);
+ void drawCloseButton(bool hovered);
+ void drawScrollbar(UIButtonState state);
// Returns the on-popup-surface bounding rect of the slider thumb at
// the current scroll position (in popup-local coords).
diff --git a/engines/nancy/ui/scrolltextbox.cpp b/engines/nancy/ui/scrolltextbox.cpp
index c3b560b9282..0c74fd0db84 100644
--- a/engines/nancy/ui/scrolltextbox.cpp
+++ b/engines/nancy/ui/scrolltextbox.cpp
@@ -188,7 +188,7 @@ void ScrollTextBox::drawContent() {
Common::Point(textLeft, viewport.top));
if (_expanded) {
- drawScrollbar(_scrollbarDragging ? 2 : (_scrollbarHovered ? 1 : 0));
+ drawScrollbar(_scrollbarDragging ? kUIButtonPressed : (_scrollbarHovered ? kUIButtonHover : kUIButtonIdle));
}
_needsRedraw = true;
@@ -232,7 +232,7 @@ Common::Rect ScrollTextBox::computeThumbRect() const {
return toPopupLocal(chunkThumb, sl.destUsesGameFrameOffset != 0);
}
-void ScrollTextBox::drawScrollbar(uint state) {
+void ScrollTextBox::drawScrollbar(UIButtonState state) {
const UISliderRecord &sl = _header->slider;
if (!_header->sliderEnabled) {
return;
@@ -325,7 +325,7 @@ void ScrollTextBox::handleInput(NancyInput &input) {
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
- drawScrollbar(overThumb ? 1 : 0);
+ drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
_needsRedraw = true;
}
@@ -334,7 +334,7 @@ void ScrollTextBox::handleInput(NancyInput &input) {
if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
_scrollbarDragging = true;
_scrollbarGrabOffset = localMouse.y - thumbY;
- drawScrollbar(2);
+ drawScrollbar(kUIButtonPressed);
_needsRedraw = true;
input.eatMouseInput();
return;
diff --git a/engines/nancy/ui/scrolltextbox.h b/engines/nancy/ui/scrolltextbox.h
index 4ab89e276da..3c94ee47ca0 100644
--- a/engines/nancy/ui/scrolltextbox.h
+++ b/engines/nancy/ui/scrolltextbox.h
@@ -22,6 +22,7 @@
#ifndef NANCY_UI_SCROLLTEXTBOX_H
#define NANCY_UI_SCROLLTEXTBOX_H
+#include "engines/nancy/commontypes.h"
#include "engines/nancy/misc/hypertext.h"
#include "engines/nancy/renderobject.h"
@@ -59,7 +60,7 @@ public:
private:
void drawBackground();
void drawContent();
- void drawScrollbar(uint state);
+ void drawScrollbar(UIButtonState state);
uint16 getInnerHeight() const;
// The text content area in popup-local coordinates (the screen-space text
Commit: 2f8d211232ba20f7a35cc94912222ddd896f1834
https://github.com/scummvm/scummvm/commit/2f8d211232ba20f7a35cc94912222ddd896f1834
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:19+03:00
Commit Message:
NANCY: Fix the hover cursor in the Nancy10+ inventory popup
Changed paths:
engines/nancy/ui/inventorypopup.cpp
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 3b5286e51af..1aafab3899b 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -308,10 +308,12 @@ void InventoryPopup::handleInput(NancyInput &input) {
input.mousePos.x - _screenPosition.left + _uiivData->header.normalDestRect.left,
input.mousePos.y - _screenPosition.top + _uiivData->header.normalDestRect.top);
- // kHotspot is the highlighted magnifier and also the cursor that
- // blends a held-item sprite on Nancy 10+, so it works in both
- // "empty hand" (plain magnifier) and "carrying" (magnifier + item) states.
- const CursorManager::CursorType hoverCursor = CursorManager::kHotspot;
+ // Item slots use kHotspot: the highlighted magnifier, which also blends a
+ // held-item sprite on Nancy 10+, so it works in both "empty hand" (plain
+ // magnifier) and "carrying" (magnifier + item) states. The popup chrome
+ // (close button, scrollbar, filter tabs) uses kHotspotArrow instead, like
+ // the other Nancy 10+ popups.
+ const CursorManager::CursorType slotCursor = CursorManager::kHotspot;
// Scrollbar interaction takes priority while dragging.
const UISliderRecord &slider = _uiivData->header.slider;
@@ -331,7 +333,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
const bool overScrollbar = thumbInChunk.contains(chunkMouse);
if (_scrollbarDragging) {
- g_nancy->_cursor->setCursorType(hoverCursor);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
const int newThumbTop = chunkMouse.y - _scrollbarGrabOffset;
const int clamped = CLIP<int>(newThumbTop, track.top, track.top + travel);
@@ -354,7 +356,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
_needsRedraw = true;
}
if (overScrollbar) {
- g_nancy->_cursor->setCursorType(hoverCursor);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
_scrollbarDragging = true;
_scrollbarGrabOffset = chunkMouse.y - thumbY;
@@ -382,7 +384,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
_needsRedraw = true;
}
if (overClose) {
- g_nancy->_cursor->setCursorType(hoverCursor);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
input.eatMouseInput();
close();
@@ -409,7 +411,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
}
if (hoveredSlot != -1) {
- g_nancy->_cursor->setCursorType(hoverCursor);
+ g_nancy->_cursor->setCursorType(slotCursor);
if (input.input & NancyInput::kLeftMouseButtonUp) {
if (heldItem != -1) {
@@ -482,7 +484,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
continue;
}
- g_nancy->_cursor->setCursorType(hoverCursor);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
drawFilterTab(i, true);
Commit: aa33763e686226a423da193433bb6bf516b39332
https://github.com/scummvm/scummvm/commit/aa33763e686226a423da193433bb6bf516b39332
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:21+03:00
Commit Message:
NANCY: Revert autotext lookup for sound names
This reverts commit 2ae68a2.
This was an incorrect understanding of this case. the * character is
actually a force flag for random sounds, and will be implemented in a
subsequent commit
Changed paths:
engines/nancy/action/soundrecords.cpp
engines/nancy/commontypes.cpp
engines/nancy/util.cpp
engines/nancy/util.h
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 1571a1b6375..76055a44abe 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -315,9 +315,6 @@ void StopSound::execute() {
void PlayRandomSound::readData(Common::SeekableReadStream &stream) {
uint16 numSounds = stream.readUint16LE();
readFilenameArray(stream, _soundNames, numSounds - 1);
- for (Common::String &n : _soundNames) {
- resolveSoundNameAutoText(n);
- }
PlaySound::readData(stream);
_soundNames.push_back(_sound.name);
@@ -334,9 +331,6 @@ void PlayRandomSound::execute() {
void PlayRandomSoundTerse::readData(Common::SeekableReadStream &stream) {
uint16 numSounds = stream.readUint16LE();
readFilenameArray(stream, _soundNames, numSounds - 1);
- for (Common::String &n : _soundNames) {
- resolveSoundNameAutoText(n);
- }
PlaySoundTerse::readData(stream);
diff --git a/engines/nancy/commontypes.cpp b/engines/nancy/commontypes.cpp
index d99d96e074a..0ba4dbe9a98 100644
--- a/engines/nancy/commontypes.cpp
+++ b/engines/nancy/commontypes.cpp
@@ -200,7 +200,6 @@ void SoundDescription::readDIGI(Common::SeekableReadStream &stream) {
s.setVersion(g_nancy->getGameType());
readFilename(s, name);
- resolveSoundNameAutoText(name);
s.syncAsUint16LE(channelID);
@@ -268,7 +267,6 @@ void SoundDescription::readScene(Common::SeekableReadStream &stream) {
void SoundDescription::readTerse(Common::SeekableReadStream &stream) {
readFilename(stream, name);
- resolveSoundNameAutoText(name);
channelID = stream.readUint16LE();
numLoops = stream.readUint32LE();
volume = stream.readUint16LE();
diff --git a/engines/nancy/util.cpp b/engines/nancy/util.cpp
index 6ec9e89a96b..6c9518635f1 100644
--- a/engines/nancy/util.cpp
+++ b/engines/nancy/util.cpp
@@ -248,16 +248,6 @@ void readFilenameArray(Common::Serializer &stream, Common::Array<Common::Path> &
}
}
-// Names with a leading '*' are CVTX (AUTOTEXT) keys for the real filename.
-void resolveSoundNameAutoText(Common::String &name) {
- if (name.empty() || name.firstChar() != '*') {
- return;
- }
- const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
- name.deleteChar(0);
- name = getTextFromCaseInsensitiveKey(autotext->texts, name);
-}
-
void readUIButton(Common::SeekableReadStream &stream, UIButtonRecord &dst) {
// Read common fields for both buttons and sliders
readFilename(stream, dst.primaryImageName);
diff --git a/engines/nancy/util.h b/engines/nancy/util.h
index 5a96d20e29e..0977792e16f 100644
--- a/engines/nancy/util.h
+++ b/engines/nancy/util.h
@@ -58,8 +58,6 @@ void readFilenameArray(Common::Serializer &stream, Common::Array<Common::String>
void readFilenameArray(Common::SeekableReadStream &stream, Common::Array<Common::Path> &inArray, uint num);
void readFilenameArray(Common::Serializer &stream, Common::Array<Common::Path> &inArray, uint num, Common::Serializer::Version minVersion = 0, Common::Serializer::Version maxVersion = Common::Serializer::kLastVersion);
-void resolveSoundNameAutoText(Common::String &name);
-
void assembleTextLine(char *rawCaption, Common::String &output, uint size);
void readUIButton(Common::SeekableReadStream &stream, UIButtonRecord &dst);
Commit: 40bb2c20a532ce0558ab191e70813debbcd36e6b
https://github.com/scummvm/scummvm/commit/40bb2c20a532ce0558ab191e70813debbcd36e6b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:22+03:00
Commit Message:
NANCY: Handle event flags and drop hardcoded data for Nancy12+ games
- Generic and game-specific flags are now handled correctly for
Nancy12+ games
- Static data is no longer saved in nancy.dat for Nancy12+ games
Conversation data for Nancy12+ is still not handled
Changed paths:
engines/nancy/console.cpp
engines/nancy/nancy.cpp
engines/nancy/nancy.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index ad1170d92ec..a8fc38dbac5 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -777,26 +777,32 @@ bool NancyConsole::Cmd_getEventFlags(int argc, const char **argv) {
uint numEventFlags = g_nancy->getStaticData().numEventFlags;
+ // Event flags are addressed by label. Nancy3 and later number them from 1000
+ // (and Nancy12+ splits them into a 1xxx generic and a 2xxx game-specific range),
+ // so the label of the flag stored at index i is i + baseLabel.
+ int baseLabel = g_nancy->getGameType() >= kGameTypeNancy3 ? 1000 : 0;
+
debugPrintf("Total number of event flags: %u\n", numEventFlags);
if (argc == 1) {
for (uint i = 0; i < numEventFlags; ++i) {
- debugPrintf("\nFlag %u, %s, %s",
- i,
- g_nancy->getEventFlagName(i).c_str(),
- NancySceneState.getEventFlag(i, g_nancy->_true) == true ? "true" : "false");
+ int label = baseLabel + (int)i;
+ debugPrintf("\nFlag %d, %s, %s",
+ label,
+ g_nancy->getEventFlagName(label).c_str(),
+ NancySceneState.getEventFlag(label, g_nancy->_true) ? "true" : "false");
}
} else {
for (int i = 1; i < argc; ++i) {
- int flagID = atoi(argv[i]);
- if (flagID < 0 || flagID >= (int)numEventFlags) {
+ int label = baseLabel + atoi(argv[i]);
+ if (label - baseLabel < 0 || label - baseLabel >= (int)numEventFlags) {
debugPrintf("\nInvalid flag %s", argv[i]);
continue;
}
- debugPrintf("\nFlag %u, %s, %s",
- flagID,
- g_nancy->getEventFlagName(flagID).c_str(),
- NancySceneState.getEventFlag(flagID, g_nancy->_true) == true ? "true" : "false");
+ debugPrintf("\nFlag %d, %s, %s",
+ label,
+ g_nancy->getEventFlagName(label).c_str(),
+ NancySceneState.getEventFlag(label, g_nancy->_true) ? "true" : "false");
}
}
@@ -813,23 +819,26 @@ bool NancyConsole::Cmd_setEventFlags(int argc, const char **argv) {
return true;
}
+ // Event flags are addressed by label (numbered from 1000 in Nancy3 and later)
+ int baseLabel = g_nancy->getGameType() >= kGameTypeNancy3 ? 1000 : 0;
+
for (int i = 1; i < argc; i += 2) {
- int flagID = atoi(argv[i]);
- if (flagID < 0 || flagID >= (int)g_nancy->getStaticData().numEventFlags) {
+ int label = baseLabel + atoi(argv[i]);
+ if (label - baseLabel < 0 || label - baseLabel >= (int)g_nancy->getStaticData().numEventFlags) {
debugPrintf("Invalid flag %s\n", argv[i]);
continue;
}
if (Common::String(argv[i + 1]).compareTo("true") == 0) {
- NancySceneState.setEventFlag(flagID, g_nancy->_true);
- debugPrintf("Set flag %i, %s, to true\n",
- flagID,
- g_nancy->getEventFlagName(flagID).c_str());
+ NancySceneState.setEventFlag(label, g_nancy->_true);
+ debugPrintf("Set flag %d, %s, to true\n",
+ label,
+ g_nancy->getEventFlagName(label).c_str());
} else if (Common::String(argv[i + 1]).compareTo("false") == 0) {
- NancySceneState.setEventFlag(flagID, g_nancy->_false);
- debugPrintf("Set flag %i, %s, to false\n",
- flagID,
- g_nancy->getEventFlagName(flagID).c_str());
+ NancySceneState.setEventFlag(label, g_nancy->_false);
+ debugPrintf("Set flag %d, %s, to false\n",
+ label,
+ g_nancy->getEventFlagName(label).c_str());
} else {
debugPrintf("Invalid value %s\n", argv[i + 1]);
continue;
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index 752918875b1..fbf783eeade 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -220,27 +220,56 @@ const EngineData *NancyEngine::getEngineData(const Common::String &name) const {
return nullptr;
}
+// From Nancy12 the event flags are split into two ranges: 1000 generic engine
+// flags (labels 1000-1999) followed by the game-specific flags (labels from 2000),
+// whose names are listed in the EVNT chunk.
+static const uint kNumGenericEventFlags = 1000;
+
+// Nancy12 keeps no names for its engine-internal flags in the 1xxx range; it
+// builds them at runtime by joining a category name with the flag's position
+// inside the category. These are the label ranges it assigns to each category.
+struct GenericEventFlagCategory {
+ uint16 firstLabel;
+ uint16 lastLabel;
+ const char *name;
+};
+
+static const GenericEventFlagCategory kGenericEventFlagCategories[] = {
+ { 1010, 1040, "Generic" },
+ { 1100, 1110, "Timer" },
+ { 1512, 1532, "Meta_Award" },
+ { 1533, 1558, "Said_Comment" },
+ { 1559, 1658, "Empty" }
+};
+
const Common::String NancyEngine::getEventFlagName(uint flagID) const {
if (getGameType() <= kGameTypeNancy11) {
+ // All flag names are stored in the executable
if (flagID >= 1000) {
// In nancy3 and onwards flags begin from 1000
flagID -= 1000;
}
return (flagID < _staticData.eventFlagNames.size()) ? _staticData.eventFlagNames[flagID] : "";
- } else {
- if (flagID >= 2000) {
- flagID -= 2000;
+ }
- auto flagNames = ((EVNT *)getEngineData("EVNT"))->eventFlagNames;
- return (flagID < flagNames.size()) ? flagNames[flagID] : "";
- } else {
- if (flagID >= 1000) {
- flagID -= 1000;
- }
+ // Nancy12 split the flags in two: the game-specific flags were moved to the
+ // EVNT chunk and renumbered from 2000, while the engine's generic flags kept
+ // their 1xxx numbering
+ if (flagID >= 2000) {
+ flagID -= 2000;
+
+ const Common::Array<Common::String> &flagNames = ((EVNT *)getEngineData("EVNT"))->eventFlagNames;
+ return (flagID < flagNames.size()) ? flagNames[flagID] : "";
+ }
- return Common::String::format("Generic%d", flagID);
+ for (uint i = 0; i < ARRAYSIZE(kGenericEventFlagCategories); ++i) {
+ const GenericEventFlagCategory &category = kGenericEventFlagCategories[i];
+ if (flagID >= category.firstLabel && flagID <= category.lastLabel) {
+ return Common::String::format("%s%u", category.name, flagID - category.firstLabel);
}
}
+
+ return "";
}
void NancyEngine::setState(NancyState::NancyState state, NancyState::NancyState overridePrevious) {
@@ -452,8 +481,14 @@ void NancyEngine::bootGameEngine() {
_resource->readCifTree("ciftree", "dat", 1);
_resource->readCifTree("promotree", "dat", 1);
- // Read nancy.dat
- readDatFile();
+ // Read the static data. Up to Nancy11 it lives in nancy.dat; from Nancy12
+ // onwards the game ships it in its own data files, so the engine only needs
+ // to provide the few remaining hardcoded values itself.
+ if (getGameType() <= kGameTypeNancy11) {
+ readDatFile();
+ } else {
+ populateStaticData();
+ }
// Setup mixer
syncSoundSettings();
@@ -569,6 +604,14 @@ void NancyEngine::bootGameEngine() {
if (getGameType() >= kGameTypeNancy12) {
LOAD_CHUNK("FLAGS", EVNT, "EVNT", "EVNT")
+
+ // The total number of event flags is the 1000 generic flags plus the
+ // game-specific flags listed in the EVNT chunk, so compute it from the
+ // game data instead of relying on the hardcoded value
+ auto *evnt = (const EVNT *)getEngineData("EVNT");
+ if (evnt) {
+ _staticData.numEventFlags = (uint16)(kNumGenericEventFlags + evnt->eventFlagNames.size());
+ }
}
// Load convo texts and autotext
@@ -715,6 +758,50 @@ void NancyEngine::readDatFile() {
delete datFile;
}
+void NancyEngine::populateStaticData() {
+ // The number of inventory items and cursor types is the only per-game data
+ // that still has to be hardcoded: both are needed to parse the INV and CURS
+ // chunks, which consume the counts rather than publishing them.
+ switch (getGameType()) {
+ case kGameTypeNancy12:
+ _staticData.numItems = 70;
+ _staticData.numCursorTypes = 37;
+ break;
+ case kGameTypeNancy13:
+ _staticData.numItems = 50;
+ _staticData.numCursorTypes = 37;
+ break;
+ case kGameTypeNancy14:
+ case kGameTypeNancy15:
+ _staticData.numItems = 50;
+ _staticData.numCursorTypes = 44;
+ break;
+ default:
+ _staticData.numItems = 50;
+ _staticData.numCursorTypes = 37;
+ break;
+ }
+
+ // Generic event flags occupy labels 1010-1040 (indices 10-40), and the
+ // won-game flag is label 1042 (index 42). numEventFlags is computed from the
+ // EVNT chunk later in bootGameEngine; this is just a fallback if it is absent.
+ _staticData.numEventFlags = kNumGenericEventFlags;
+ _staticData.genericEventFlags.resize(31);
+ for (uint i = 0; i < _staticData.genericEventFlags.size(); ++i) {
+ _staticData.genericEventFlags[i] = 10 + i;
+ }
+ _staticData.wonGameFlagID = 42;
+ _staticData.logoEndAfter = 4000;
+
+ // Sound channel layout, unchanged since Nancy3
+ SoundChannelInfo &sci = _staticData.soundChannelInfo;
+ sci.numChannels = 32;
+ sci.numSceneSpecificChannels = 14;
+ sci.speechChannels = { 12, 13, 30 };
+ sci.musicChannels = { 0, 1, 2, 3, 19, 26, 27, 29 };
+ sci.sfxChannels = { 4, 5, 6, 7, 8, 9, 10, 11, 17, 18, 20, 21, 22, 23, 24, 25, 31 };
+}
+
Common::Error NancyEngine::synchronize(Common::Serializer &ser) {
auto *bootSummary = GetEngineData(BSUM);
assert(bootSummary);
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index 39722253d4d..e207cf4ae2c 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -144,6 +144,9 @@ private:
void preloadCals();
void readDatFile();
+ // Nancy12 onwards no longer ship their static data in nancy.dat; the values
+ // the engine still needs are provided here instead (see also the EVNT chunk).
+ void populateStaticData();
Common::Error synchronize(Common::Serializer &serializer);
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 88f17c4d29e..43d383c404d 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -541,19 +541,23 @@ void Scene::playItemCantSound(int16 itemID, bool notHoldingSound) {
}
}
-void Scene::setEventFlag(int16 label, byte flag) {
- if (label >= 2000) {
- // In nancy12 and onwards flags begin from 2000
- label -= 2000;
- }
-
+int16 Scene::eventFlagToIndex(int16 label) const {
+ // Nancy3+ number their event flags from 1000. Nancy12 then split the flags
+ // into two ranges: the generic engine flags kept the 1xxx numbering, while
+ // the game-specific flags were renumbered from 2000. Subtracting a flat 1000
+ // from any 1xxx/2xxx label keeps the two ranges in separate, non-overlapping
+ // regions of the flags array (generic flags in [0, 1000), game-specific flags
+ // in [1000, ...)).
if (label >= 1000) {
- // In nancy3 and onwards flags begin from 1000
- // TODO: We need to separate the generic event flags
- // from the normal event flags in Nancy12+
label -= 1000;
}
+ return label;
+}
+
+void Scene::setEventFlag(int16 label, byte flag) {
+ label = eventFlagToIndex(label);
+
if (label > kEvNoEvent && (uint)label < g_nancy->getStaticData().numEventFlags) {
_flags.eventFlags[label] = flag;
}
@@ -564,17 +568,7 @@ void Scene::setEventFlag(FlagDescription eventFlag) {
}
bool Scene::getEventFlag(int16 label, byte flag) const {
- if (label >= 2000) {
- // In nancy12 and onwards flags begin from 2000
- label -= 2000;
- }
-
- if (label >= 1000) {
- // In nancy3 and onwards flags begin from 1000
- // TODO: We need to separate the generic event flags
- // from the normal event flags in Nancy12+
- label -= 1000;
- }
+ label = eventFlagToIndex(label);
if (label > kEvNoEvent && (uint)label < g_nancy->getStaticData().numEventFlags) {
return _flags.eventFlags[label] == flag;
@@ -587,14 +581,16 @@ bool Scene::getEventFlag(FlagDescription eventFlag) const {
return getEventFlag(eventFlag.label, eventFlag.flag);
}
-// Nancy 11+ AR 30/31 store the "player scrolling disabled" state in event flag
-// 1033 (eventData[0x21] in the original; nancy3+ event-flag labels start at
-// 1000, so this is index 33). It persists across scenes and is saved/restored
-// together with the rest of the event flags.
-static const int16 kPlayerScrollingDisabledFlag = 1033;
+// Nancy 11+ AR 30/31 store the "player scrolling disabled" state in an event
+// flag (eventData[0x21] in the original). It persists across scenes and is
+// saved/restored together with the rest of the event flags. Nancy12 shifted the
+// engine's generic flag numbering up by 10, moving this flag from 1033 to 1043.
+static int16 playerScrollingDisabledFlag() {
+ return g_nancy->getGameType() >= kGameTypeNancy12 ? 1043 : 1033;
+}
void Scene::setPlayerScrolling(bool enabled) {
- setEventFlag(kPlayerScrollingDisabledFlag, enabled ? g_nancy->_false : g_nancy->_true);
+ setEventFlag(playerScrollingDisabledFlag(), enabled ? g_nancy->_false : g_nancy->_true);
}
bool Scene::getPlayerScrolling() const {
@@ -604,7 +600,7 @@ bool Scene::getPlayerScrolling() const {
return true;
}
- return !getEventFlag(kPlayerScrollingDisabledFlag, g_nancy->_true);
+ return !getEventFlag(playerScrollingDisabledFlag(), g_nancy->_true);
}
void Scene::setLogicCondition(int16 label, byte flag) {
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 68c16600899..3429f670727 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -260,6 +260,9 @@ private:
void clearSceneData();
void clearPuzzleData();
+ // Maps an event flag label to its index in the eventFlags array
+ int16 eventFlagToIndex(int16 label) const;
+
struct SceneState {
SceneSummary summary;
SceneChangeDescription currentScene;
Commit: 75c43fb0226ebf962c6fb64c95f53966d63b5da3
https://github.com/scummvm/scummvm/commit/75c43fb0226ebf962c6fb64c95f53966d63b5da3
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:25+03:00
Commit Message:
DEVTOOLS: Remove static data for Nancy12+ games and regen nancy.dat
In Nancy 12 and newer games, static data has been moved from the
executable to game data files, so we no longer need to store it in
nancy.dat
Changed paths:
R devtools/create_nancy/nancy12_data.h
R devtools/create_nancy/nancy13_data.h
R devtools/create_nancy/nancy14_data.h
R devtools/create_nancy/nancy15_data.h
devtools/create_nancy/create_nancy.cpp
dists/engine-data/nancy.dat
diff --git a/devtools/create_nancy/create_nancy.cpp b/devtools/create_nancy/create_nancy.cpp
index e6a2776bbb2..a1eefb0e025 100644
--- a/devtools/create_nancy/create_nancy.cpp
+++ b/devtools/create_nancy/create_nancy.cpp
@@ -33,15 +33,13 @@
#include "nancy9_data.h"
#include "nancy10_data.h"
#include "nancy11_data.h"
-#include "nancy12_data.h"
-#include "nancy13_data.h"
-#include "nancy14_data.h"
-#include "nancy15_data.h"
#define NANCYDAT_MAJOR_VERSION 1
#define NANCYDAT_MINOR_VERSION 1
-#define NANCYDAT_NUM_GAMES 16
+// From Nancy12 onwards the static data is shipped in the games' own data files
+// instead of the executable, so nancy.dat only carries data up to Nancy11.
+#define NANCYDAT_NUM_GAMES 12
/**
* Format specifications for nancy.dat:
@@ -81,10 +79,9 @@
* Nancy Drew: Danger on Deception Island
* Nancy Drew: The Secret of Shadow Ranch
* Nancy Drew: Curse of Blackmoor Manor
- * Nancy Drew: Secret of the Old Clock
- * Nancy Drew: Last Train to Blue Moon Canyon
- * Nancy Drew: Danger by Design
- * Nancy Drew: The Creature of Kapu Cave
+ *
+ * Nancy12 (Secret of the Old Clock) and newer ship their static data in their
+ * own data files, so they are not included here.
*/
// Add the offset to the next tagged section before the section itself for easier navigation
@@ -354,45 +351,8 @@ int main(int argc, char *argv[]) {
WRAPWITHOFFSET(writeRingingTexts(output, _nancy8TelephoneRinging)) // same as 8
WRAPWITHOFFSET(writeEventFlagNames(output, _nancy11EventFlagNames))
- // Nancy Drew: Secret of the Old Clock
- gameOffsets.push_back(output.pos());
- WRAPWITHOFFSET(writeConstants(output, _nancy12Constants))
- WRAPWITHOFFSET(writeSoundChannels(output, _nancy3andUpSoundChannelInfo)) // same as 3
- WRAPWITHOFFSET(writeLanguages(output, _nancy8LanguagesOrder)) // same as 8
- WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy12ConditionalDialogue))
- WRAPWITHOFFSET(writeGoodbyes(output, _nancy12Goodbyes))
- WRAPWITHOFFSET(writeRingingTexts(output, _nancy8TelephoneRinging)) // same as 8
- WRAPWITHOFFSET(writeEventFlagNames(output, _nancy12EventFlagNames))
-
- // Nancy Drew: Last Train to Blue Moon Canyon
- gameOffsets.push_back(output.pos());
- WRAPWITHOFFSET(writeConstants(output, _nancy13Constants))
- WRAPWITHOFFSET(writeSoundChannels(output, _nancy3andUpSoundChannelInfo)) // same as 3
- WRAPWITHOFFSET(writeLanguages(output, _nancy8LanguagesOrder)) // same as 8
- WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy12ConditionalDialogue)) // same as 12
- WRAPWITHOFFSET(writeGoodbyes(output, _nancy12Goodbyes)) // same as 12
- WRAPWITHOFFSET(writeRingingTexts(output, _nancy8TelephoneRinging)) // same as 8
- WRAPWITHOFFSET(writeEventFlagNames(output, _nancy12EventFlagNames)) // same as 12
-
- // Nancy Drew: Danger by Design
- gameOffsets.push_back(output.pos());
- WRAPWITHOFFSET(writeConstants(output, _nancy14Constants))
- WRAPWITHOFFSET(writeSoundChannels(output, _nancy3andUpSoundChannelInfo)) // same as 3
- WRAPWITHOFFSET(writeLanguages(output, _nancy8LanguagesOrder)) // same as 8
- WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy12ConditionalDialogue)) // same as 12
- WRAPWITHOFFSET(writeGoodbyes(output, _nancy12Goodbyes)) // same as 12
- WRAPWITHOFFSET(writeRingingTexts(output, _nancy8TelephoneRinging)) // same as 8
- WRAPWITHOFFSET(writeEventFlagNames(output, _nancy12EventFlagNames)) // same as 12
-
- // Nancy Drew: The Creature of Kapu Cave
- gameOffsets.push_back(output.pos());
- WRAPWITHOFFSET(writeConstants(output, _nancy15Constants))
- WRAPWITHOFFSET(writeSoundChannels(output, _nancy3andUpSoundChannelInfo)) // same as 3
- WRAPWITHOFFSET(writeLanguages(output, _nancy8LanguagesOrder)) // same as 8
- WRAPWITHOFFSET(writeConditionalDialogue(output, _nancy12ConditionalDialogue)) // same as 12
- WRAPWITHOFFSET(writeGoodbyes(output, _nancy12Goodbyes)) // same as 12
- WRAPWITHOFFSET(writeRingingTexts(output, _nancy8TelephoneRinging)) // same as 8
- WRAPWITHOFFSET(writeEventFlagNames(output, _nancy12EventFlagNames)) // same as 12
+ // Nancy12 and newer no longer store their static data in the executable, so
+ // there is nothing to write for them here (the engine provides what it needs).
// Write the offsets for each game in the header
output.seek(offsetsOffset);
diff --git a/devtools/create_nancy/nancy12_data.h b/devtools/create_nancy/nancy12_data.h
deleted file mode 100644
index efab0a8ad95..00000000000
--- a/devtools/create_nancy/nancy12_data.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* 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 NANCY12DATA_H
-#define NANCY12DATA_H
-
-#include "types.h"
-
-const GameConstants _nancy12Constants ={
- 70, // numItems
- 1251, // numEventFlags - TODO: verify this
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // genericEventFlags
- 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
- 37, // numCursorTypes
- 4000, // logoEndAfter
- 32 // wonGameFlagID
-};
-
-// Conditional dialog checks and goodbyes have been moved to game data files
-const Common::Array<Common::Array<ConditionalDialogue>> _nancy12ConditionalDialogue = {};
-const Common::Array<Goodbye> _nancy12Goodbyes = {};
-
-// Event flag names are no longer stored in the executable
-const Common::Array<const char *> _nancy12EventFlagNames = {};
-
-#endif // NANCY12DATA_H
diff --git a/devtools/create_nancy/nancy13_data.h b/devtools/create_nancy/nancy13_data.h
deleted file mode 100644
index aa7077ec490..00000000000
--- a/devtools/create_nancy/nancy13_data.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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 NANCY13DATA_H
-#define NANCY13DATA_H
-
-#include "types.h"
-
-const GameConstants _nancy13Constants ={
- 50, // numItems
- 1251, // numEventFlags - TODO: verify this
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // genericEventFlags
- 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
- 37, // numCursorTypes
- 4000, // logoEndAfter
- 32 // wonGameFlagID
-};
-
-#endif // NANCY13DATA_H
diff --git a/devtools/create_nancy/nancy14_data.h b/devtools/create_nancy/nancy14_data.h
deleted file mode 100644
index ae7a1301cc3..00000000000
--- a/devtools/create_nancy/nancy14_data.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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 NANCY14DATA_H
-#define NANCY14DATA_H
-
-#include "types.h"
-
-const GameConstants _nancy14Constants ={
- 50, // numItems
- 1251, // numEventFlags - TODO: verify this
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // genericEventFlags
- 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
- 44, // numCursorTypes
- 4000, // logoEndAfter
- 32 // wonGameFlagID
-};
-
-#endif // NANCY14DATA_H
diff --git a/devtools/create_nancy/nancy15_data.h b/devtools/create_nancy/nancy15_data.h
deleted file mode 100644
index 3280ed52a60..00000000000
--- a/devtools/create_nancy/nancy15_data.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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 NANCY15DATA_H
-#define NANCY15DATA_H
-
-#include "types.h"
-
-const GameConstants _nancy15Constants ={
- 50, // numItems
- 1251, // numEventFlags - TODO: verify this
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // genericEventFlags
- 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
- 44, // numCursorTypes
- 4000, // logoEndAfter
- 32 // wonGameFlagID
-};
-
-#endif // NANCY15DATA_H
diff --git a/dists/engine-data/nancy.dat b/dists/engine-data/nancy.dat
index aad04f0d99b..8383f1d36c0 100644
Binary files a/dists/engine-data/nancy.dat and b/dists/engine-data/nancy.dat differ
Commit: 6f4bae2f16afc7f22cbf2d38c27e6b9514acd83d
https://github.com/scummvm/scummvm/commit/6f4bae2f16afc7f22cbf2d38c27e6b9514acd83d
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:28+03:00
Commit Message:
NANCY: Fix hover cursor value checked for puzzles in Nancy10+
Changed paths:
engines/nancy/action/datarecords.cpp
engines/nancy/cursor.h
diff --git a/engines/nancy/action/datarecords.cpp b/engines/nancy/action/datarecords.cpp
index b5ac2380c8c..a4824a7598e 100644
--- a/engines/nancy/action/datarecords.cpp
+++ b/engines/nancy/action/datarecords.cpp
@@ -386,7 +386,7 @@ void EventFlagsMultiHS::execute() {
// Swallow clicks if the cursor is in the puzzle-drag range
if (g_nancy->getGameType() <= kGameTypeNancy9 && (_hoverCursor == CursorManager::kCustom1 || _hoverCursor == CursorManager::kCustom2)) {
_state = kRun;
- } else if (g_nancy->getGameType() >= kGameTypeNancy10 && (int)_hoverCursor >= 35) {
+ } else if (g_nancy->getGameType() >= kGameTypeNancy10 && (int)_hoverCursor >= CursorManager::kNewUseHand) {
_state = kRun;
} else {
_hasHotspot = false;
diff --git a/engines/nancy/cursor.h b/engines/nancy/cursor.h
index b91b08bca1f..30d22e62ec0 100644
--- a/engines/nancy/cursor.h
+++ b/engines/nancy/cursor.h
@@ -88,6 +88,7 @@ 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
kNewDragHand = 38, // Type 19 â Hand used while dragging puzzle pieces (e.g. SortPuzzle pickup action sets this)
kNewPuzzleArrow = 45, // Type 22 hotspot â Arrow cursor shown when hovering a clickable puzzle hotspot
kNewDropHand = 64, // Type 32 â Hand shown when a held piece is dropped (briefly set on the drop action)
Commit: a35fb9aacd27846ad040f28d7d65896fe7e27a5d
https://github.com/scummvm/scummvm/commit/a35fb9aacd27846ad040f28d7d65896fe7e27a5d
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:37+03:00
Commit Message:
NANCY: Add another case for case-insensitive searching in Nancy11+
Changed paths:
engines/nancy/util.cpp
diff --git a/engines/nancy/util.cpp b/engines/nancy/util.cpp
index 6c9518635f1..c8b13d726a1 100644
--- a/engines/nancy/util.cpp
+++ b/engines/nancy/util.cpp
@@ -359,8 +359,15 @@ Common::String getTextFromCaseInsensitiveKey(Common::HashMap<Common::String, Com
if (texts.contains(key))
return texts[key];
+ // Try all uppercase
key.toUppercase();
+ if (texts.contains(key))
+ return texts[key];
+
+ // Check for lowercase cases for the second to last character, for Nancy11+
+ key[key.size() - 2] = tolower(key[key.size() - 2]);
+
if (texts.contains(key))
return texts[key];
}
Commit: 5cc80b20e0c695e0adbb6cf27743350962416df0
https://github.com/scummvm/scummvm/commit/5cc80b20e0c695e0adbb6cf27743350962416df0
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-27T12:50:38+03:00
Commit Message:
NANCY: Cleanup comments
Changed paths:
engines/nancy/action/miscrecords.cpp
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index 3f0d9f77eb4..df51c755226 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -225,11 +225,9 @@ void FrameTextBox::execute() {
tb.addTextLine(_text);
}
- // Variant 74 (case 0x4a in ProcessActionRecords) opens the full-width
- // textbox overlay that covers the taskbar buttons; the original arms a
- // 15-second timer (DAT_005a7a7d = GetTickCount + 15000) that drops it
- // back to the closed strip. Variant 75 (case 0x4b) is the legacy
- // closed/strip path.
+ // Variant 74 opens the full-width textbox overlay that covers the taskbar
+ // buttons; the original arms a 15-second timer that drops it back to the
+ // closed strip. Variant 75 is the legacy closed/strip path.
if (_variant == kVariant74) {
tb.setFullMode(true);
} else {
More information about the Scummvm-git-logs
mailing list