[Scummvm-git-logs] scummvm master -> 1fd05cea96680bd707a00daef1a02f8df480eeb5

bluegr noreply at scummvm.org
Mon Jun 1 23:18:27 UTC 2026


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

Summary:
2a05646354 NANCY: More work on the Nancy10+ taskbar buttons
d68decbb6d NANCY: Add workaround for hotspots in the Feeding Frenzy game in Nancy9
10d1caf9d7 NANCY: Fix item cursor when hovering over some hotspots in Nancy10+
24dda66e77 NANCY: Disable global font kerning, as the engine has its own kerning
1fd05cea96 NANCY: More work on cellphone search link records for Nancy10+


Commit: 2a05646354759539765b4679131601fc02a89467
    https://github.com/scummvm/scummvm/commit/2a05646354759539765b4679131601fc02a89467
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-02T02:18:10+03:00

Commit Message:
NANCY: More work on the Nancy10+ taskbar buttons

- Buttons are now enabled/disabled correctly
- Added (still unused) functionality for button notifications
- More work on the ControUIItems AR, which toggles the taskbar buttons

Changed paths:
    engines/nancy/action/miscrecords.cpp
    engines/nancy/action/miscrecords.h
    engines/nancy/state/scene.cpp
    engines/nancy/ui/taskbar.cpp
    engines/nancy/ui/taskbar.h


diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index 5c373c50fb5..b7bab4de98f 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -191,17 +191,65 @@ void FrameTextBox::execute() {
 
 void ControlUIItems::readData(Common::SeekableReadStream &stream) {
 	_uiButton = stream.readUint16LE();
-	_flagA = stream.readByte();
+	_autoOpenOrBadgeSound = stream.readByte();
 	_flagB  = stream.readByte();
-	_scene1 = stream.readSint16LE();
-	_scene2 = stream.readSint16LE();
+	_startScene = stream.readSint16LE();
+	_endScene = stream.readSint16LE();
 }
 
 void ControlUIItems::execute() {
-	// TODO: finish this
-
-	NancySceneState.getTaskbar()->toggleButton(_uiButton, _flagA != 0);
-	debug("ControlUIItems: UIButton=%d, flagA=%d, flagB=%d, scene1=%d, scene2=%d", _uiButton, _flagA, _flagB, _scene1, _scene2);
+	// Value 1 auto-opens the popup selected by _uiButton. For the cell
+	// phone, _startScene (when set) is the scene to jump to once it opens,
+	// which places a call that starts a conversation there.
+	if (_autoOpenOrBadgeSound == 1) {
+		switch (_uiButton) {
+		case kUITypeInventory:
+			NancySceneState.getInventoryPopup().open();
+			break;
+		case kUITypeNotebook:
+			NancySceneState.getNotebookPopup().open();
+			break;
+		case kUITypeCellphone:
+			NancySceneState.getCellPhonePopup().open();
+
+			if (_startScene != (int16)kNoScene) {
+				SceneChangeDescription scene;
+				scene.sceneID = _startScene;
+				scene.frameID = 0;
+				scene.verticalOffset = 0;
+				// The destination scene's sound carries the conversation audio.
+				scene.continueSceneSound = kLoadSceneSound;
+
+				// Pushed so AR 128 in the conversation scene can return here.
+				NancySceneState.pushScene();
+				NancySceneState.changeScene(scene);
+			}
+			break;
+		default:
+			break;
+		}
+	} else {
+		// Otherwise this AR toggles whether the button is disabled while the
+		// player is in scene range [_startScene, _endScene]. _flagB != 0 sets
+		// the toggle (a _startScene of 9997 means "from scene 0", with the
+		// range capped at 9997); _flagB == 0 clears it once a bound is 9999
+		// (kNoScene). The _autoOpenOrBadgeSound value selects the button's
+		// click sound in the original (not yet implemented).
+		UI::Taskbar *taskbar = NancySceneState.getTaskbar();
+		if (taskbar) {
+			if (_flagB != 0) {
+				int16 start = _startScene;
+				int16 end = _endScene;
+				if (_startScene == 9997) {
+					start = 0;
+					end = 9997;
+				}
+				taskbar->setDisabledRange(_uiButton, start, end);
+			} else if (_startScene == (int16)kNoScene || _endScene == (int16)kNoScene) {
+				taskbar->clearButtonOverride(_uiButton);
+			}
+		}
+	}
 
 	finishExecution();
 }
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index ed6cd179032..427bb7e5b1c 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -148,10 +148,10 @@ public:
 	void execute() override;
 
 	uint16 _uiButton = 0;
-	byte _flagA = 0;    // 0, 1 or 10
+	byte _autoOpenOrBadgeSound = 0; // 1 = auto-open popup; 0/10 = notification-badge click-sound selector
 	byte _flagB = 0;    // 0 = clear, 1 = enable+remember scene
-	int16 _scene1 = 0;  // start scene id (9999 = none)
-	int16 _scene2 = 0;  // end scene id (9999 = none)
+	int16 _startScene = 0; // start scene id (9999 = none); also the auto-open cell phone's call target
+	int16 _endScene = 0;   // end scene id (9999 = none)
 
 protected:
 	Common::String getRecordTypeName() const override { return "ControlUIItems"; }
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 3cf4d222c41..02cefa63196 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1059,11 +1059,9 @@ void Scene::load(bool fromSaveFile) {
 		_flags.sceneCounts.getOrCreateVal(_sceneState.currentScene.sceneID)++;
 	}
 
-	// Enable all task buttons
-	// TODO: This should be done elsewhere
+	// Re-evaluate taskbar notification states against the new scene.
 	if (g_nancy->getGameType() >= kGameTypeNancy10) {
-		for (int i = 0; i < 5; ++i)
-			_taskbar->toggleButton(i, true);
+		_taskbar->updateNotificationStates(_sceneState.currentScene.sceneID);
 	}
 
 	delete sceneIFF;
diff --git a/engines/nancy/ui/taskbar.cpp b/engines/nancy/ui/taskbar.cpp
index 995c1d0798e..d266c2a62bd 100644
--- a/engines/nancy/ui/taskbar.cpp
+++ b/engines/nancy/ui/taskbar.cpp
@@ -34,9 +34,11 @@ namespace UI {
 Taskbar::Taskbar() :
 		RenderObject(7),
 		_hoveredButton(-1),
-		_clickedButton(-1) {
+		_clickedButton(-1),
+		_currentScene(-1) {
 	for (uint i = 0; i < TASK::kNumButtons; ++i) {
 		_buttonStates[i] = kButtonIdle;
+		_enabled[i] = true;
 	}
 }
 
@@ -72,9 +74,14 @@ void Taskbar::drawButton(uint index, ButtonState state) {
 	auto *taskData = GetEngineData(TASK);
 	assert(taskData);
 
-	const UIButtonRecord &btn = taskData->buttons[index].button;
+	const TASK::ButtonRecord &rec = taskData->buttons[index];
+	const UIButtonRecord &btn = rec.button;
 
-	Common::Rect src = btn.sourceRects[state];
+	// The notification sprite lives outside the standard sourceRects
+	// array, in its own per-button rect.
+	Common::Rect src = (state == kButtonNotification)
+		? rec.notificationSrcRect
+		: btn.sourceRects[state];
 	if (src.isEmpty())
 		src = btn.sourceRects[kButtonIdle];
 	if (src.isEmpty())
@@ -96,8 +103,86 @@ void Taskbar::drawButton(uint index, ButtonState state) {
 	_needsRedraw = true;
 }
 
+Taskbar::ButtonState Taskbar::restingState(uint index) const {
+	if (index >= TASK::kNumButtons) {
+		return kButtonIdle;
+	}
+	if (!_enabled[index]) {
+		return kButtonDisabled;
+	}
+	const ButtonOverride &o = _overrides[index];
+	if (o.active && _currentScene >= o.startScene && _currentScene <= o.endScene) {
+		return o.state;
+	}
+	return kButtonIdle;
+}
+
+bool Taskbar::isButtonActive(uint index) const {
+	return _enabled[index] && restingState(index) != kButtonDisabled;
+}
+
 void Taskbar::toggleButton(uint index, bool enabled) {
-	drawButton(index, enabled ? kButtonIdle : kButtonDisabled);
+	if (index >= TASK::kNumButtons) {
+		return;
+	}
+	_enabled[index] = enabled;
+	if ((int)index != _hoveredButton) {
+		drawButton(index, restingState(index));
+	}
+}
+
+void Taskbar::setNotification(uint buttonIndex, int16 startScene, int16 endScene) {
+	if (buttonIndex >= TASK::kNumButtons) {
+		return;
+	}
+	_overrides[buttonIndex].active = true;
+	_overrides[buttonIndex].state = kButtonNotification;
+	_overrides[buttonIndex].startScene = startScene;
+	_overrides[buttonIndex].endScene = endScene;
+
+	// Re-render this button immediately unless the player is currently
+	// hovering it (hover takes priority over the override sprite).
+	if ((int)buttonIndex != _hoveredButton) {
+		drawButton(buttonIndex, restingState(buttonIndex));
+	}
+}
+
+void Taskbar::setDisabledRange(uint buttonIndex, int16 startScene, int16 endScene) {
+	if (buttonIndex >= TASK::kNumButtons) {
+		return;
+	}
+	_overrides[buttonIndex].active = true;
+	_overrides[buttonIndex].state = kButtonDisabled;
+	_overrides[buttonIndex].startScene = startScene;
+	_overrides[buttonIndex].endScene = endScene;
+
+	if ((int)buttonIndex != _hoveredButton) {
+		drawButton(buttonIndex, restingState(buttonIndex));
+	}
+}
+
+void Taskbar::clearButtonOverride(uint buttonIndex) {
+	if (buttonIndex >= TASK::kNumButtons) {
+		return;
+	}
+	_overrides[buttonIndex].active = false;
+
+	if ((int)buttonIndex != _hoveredButton) {
+		drawButton(buttonIndex, restingState(buttonIndex));
+	}
+}
+
+void Taskbar::updateNotificationStates(int16 currentSceneID) {
+	_currentScene = currentSceneID;
+	for (uint i = 0; i < TASK::kNumButtons; ++i) {
+		if ((int)i == _hoveredButton) {
+			continue;
+		}
+		const ButtonState desired = restingState(i);
+		if (_buttonStates[i] != desired) {
+			drawButton(i, desired);
+		}
+	}
 }
 
 void Taskbar::handleInput(NancyInput &input) {
@@ -108,18 +193,18 @@ void Taskbar::handleInput(NancyInput &input) {
 
 	int newHovered = -1;
 	for (uint i = 0; i < TASK::kNumButtons; ++i) {
-		if (taskData->buttons[i].button.destRect.contains(input.mousePos) && _buttonStates[i] != kButtonDisabled) {
+		if (isButtonActive(i) && taskData->buttons[i].button.destRect.contains(input.mousePos)) {
 			newHovered = i;
 			break;
 		}
 	}
 
-	// Update hover graphic on enter/exit. Always revert the previously-
-	// hovered button (even from kButtonPressed) so it never gets stuck
-	// after the cursor leaves.
+	// Update hover graphic on enter/exit. The previously-hovered button
+	// returns to its resting sprite (idle or notification) so it doesn't
+	// get stuck in hover/pressed after the cursor leaves.
 	if (newHovered != _hoveredButton) {
 		if (_hoveredButton != -1) {
-			drawButton(_hoveredButton, kButtonIdle);
+			drawButton(_hoveredButton, restingState(_hoveredButton));
 		}
 		if (newHovered != -1) {
 			drawButton(newHovered, kButtonHover);
@@ -141,6 +226,12 @@ void Taskbar::handleInput(NancyInput &input) {
 	} else if (input.input & NancyInput::kLeftMouseButtonUp) {
 		// Mouse released over the button: trigger the click action and
 		// snap the sprite back to hover (the cursor is still over it).
+		// Acknowledging the click also clears a pending notification
+		// for this button, so re-opening the popup doesn't keep blinking.
+		// A scene-ranged disable override is left intact.
+		if (_overrides[newHovered].state == kButtonNotification) {
+			_overrides[newHovered].active = false;
+		}
 		drawButton(newHovered, kButtonHover);
 		_clickedButton = newHovered;
 
diff --git a/engines/nancy/ui/taskbar.h b/engines/nancy/ui/taskbar.h
index d3cfcf7cb65..f8b1d92dd22 100644
--- a/engines/nancy/ui/taskbar.h
+++ b/engines/nancy/ui/taskbar.h
@@ -42,27 +42,61 @@ public:
 	void registerGraphics() override;
 	void handleInput(NancyInput &input);
 
+	// Enable / disable a taskbar button. A disabled button is rendered
+	// in its disabled sprite and ignores clicks.
 	void toggleButton(uint index, bool enabled);
 
+	// Configure a per-button override that is active only while the player
+	// is in a scene whose ID falls in [startScene, endScene]. The button
+	// renders in its notification (badge) sprite, or its disabled sprite,
+	// for that range; outside it reverts to idle.
+	// setDisabledRange is driven by AR 29 (ControlUIItems, _flagB != 0).
+	// setNotification renders the badge sprite; the source that should drive
+	// it has not been identified yet (it is NOT ControlUIItems).
+	void setNotification(uint buttonIndex, int16 startScene, int16 endScene);
+	void setDisabledRange(uint buttonIndex, int16 startScene, int16 endScene);
+	void clearButtonOverride(uint buttonIndex);
+
+	// Re-evaluate which buttons should currently show their override
+	// sprite. Call after a scene change so the range check kicks in.
+	void updateNotificationStates(int16 currentSceneID);
+
 	// Returns the index of the button that was clicked this frame, or -1
 	// if none. Cleared on the next call to handleInput().
 	int getClickedButton() const { return _clickedButton; }
 
 private:
 	enum ButtonState {
-		kButtonIdle     = 0,
-		kButtonHover    = 1,
-		kButtonPressed  = 2,
-		kButtonDisabled = 3
+		kButtonIdle         = 0,
+		kButtonHover        = 1,
+		kButtonPressed      = 2,
+		kButtonDisabled     = 3,   // not clickable
+		kButtonNotification = 4    // popup has new content (badge sprite)
+	};
+
+	// A scene-ranged sprite override for one button. While active and the
+	// current scene is within [startScene, endScene] the button renders in
+	// `state` (kButtonNotification or kButtonDisabled).
+	struct ButtonOverride {
+		bool active = false;
+		ButtonState state = kButtonIdle;
+		int16 startScene = -1;
+		int16 endScene = -1;
 	};
 
 	void drawButton(uint index, ButtonState state);
+	ButtonState restingState(uint index) const;
+	// True when the button currently accepts hover/click (not disabled).
+	bool isButtonActive(uint index) const;
 
 	Graphics::ManagedSurface _backgroundImage; // TASK::imageName (e.g. "Frame")
 	Graphics::ManagedSurface _buttonImage;     // buttons' primaryImageName (e.g. "UIShared_OVL")
 	int _hoveredButton;
 	int _clickedButton;
+	int16 _currentScene;
+	bool _enabled[5];
 	ButtonState _buttonStates[5];
+	ButtonOverride _overrides[5];
 };
 
 } // End of namespace UI


Commit: d68decbb6d65b724185b3bf988edb5d121194f05
    https://github.com/scummvm/scummvm/commit/d68decbb6d65b724185b3bf988edb5d121194f05
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-02T02:18:12+03:00

Commit Message:
NANCY: Add workaround for hotspots in the Feeding Frenzy game in Nancy9

Fix #16792

Changed paths:
    engines/nancy/action/interactivevideo.cpp


diff --git a/engines/nancy/action/interactivevideo.cpp b/engines/nancy/action/interactivevideo.cpp
index 3ee0cd42bd4..b50851bf8e8 100644
--- a/engines/nancy/action/interactivevideo.cpp
+++ b/engines/nancy/action/interactivevideo.cpp
@@ -55,6 +55,19 @@ void InteractiveVideo::readData(Common::SeekableReadStream &stream) {
 
 	readFilename(*ivFile, _videoName);
 
+	// WORKAROUND: In Nancy 9, the Feeding Frenzy mini-game plays 6 videos (the whales that pop up)
+	// with a normal arrow cursor. In such cases, the cursor manager reverts to the default arrow
+	// cursor, if an item is held (which, in this case is a fish). We replace these cursors with a
+	// normal one, which allows for the held item to be visible. Making this a workaround for that
+	// scene, since a change in that part of the cursor code would require a full regression test in
+	// all supported games. Fixes bug #16792.
+	const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
+	if (g_nancy->getGameType() == kGameTypeNancy9 && (sceneId == 2992 || sceneId == 2995 || sceneId == 2996)) {
+		if (_videoName.toString().contains("WhaleFeed") && _cursors[0] == CursorManager::kNormalArrow) {
+			_cursors[0] = CursorManager::kNormal;
+		}
+	}
+
 	uint32 numFrames = ivFile->readUint32LE();
 	_frames.resize(numFrames);
 	for (uint i = 0; i < numFrames; ++i) {


Commit: 10d1caf9d70dd6ae8210cebd2817c154616fb93e
    https://github.com/scummvm/scummvm/commit/10d1caf9d70dd6ae8210cebd2817c154616fb93e
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-02T02:18:13+03:00

Commit Message:
NANCY: Fix item cursor when hovering over some hotspots in Nancy10+

Fixes the cursor when hovering the key over the rolltop in Nancy10

Changed paths:
    engines/nancy/action/datarecords.cpp
    engines/nancy/action/datarecords.h


diff --git a/engines/nancy/action/datarecords.cpp b/engines/nancy/action/datarecords.cpp
index 65f2009ce91..6610f500be0 100644
--- a/engines/nancy/action/datarecords.cpp
+++ b/engines/nancy/action/datarecords.cpp
@@ -361,6 +361,12 @@ void EventFlagsMultiHS::readData(Common::SeekableReadStream &stream) {
 	}
 }
 
+CursorManager::CursorType EventFlagsMultiHS::getHoverCursor() const {
+	if (g_nancy->getGameType() >= kGameTypeNancy10 && NancySceneState.getHeldItem() >= 0)
+		return CursorManager::kHotspot;
+	return _hoverCursor;
+}
+
 void EventFlagsMultiHS::execute() {
 	switch (_state) {
 	case kBegin:
diff --git a/engines/nancy/action/datarecords.h b/engines/nancy/action/datarecords.h
index d8a5700ab42..d200227d2f6 100644
--- a/engines/nancy/action/datarecords.h
+++ b/engines/nancy/action/datarecords.h
@@ -120,7 +120,7 @@ public:
 	void readData(Common::SeekableReadStream &stream) override;
 	void execute() override;
 
-	CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
+	CursorManager::CursorType getHoverCursor() const override;
 
 	CursorManager::CursorType _hoverCursor = CursorManager::kHotspot;
 	Common::Array<HotspotDescription> _hotspots;


Commit: 24dda66e7783fad9d73b5f71dbfe73cea2e0b05b
    https://github.com/scummvm/scummvm/commit/24dda66e7783fad9d73b5f71dbfe73cea2e0b05b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-02T02:18:14+03:00

Commit Message:
NANCY: Disable global font kerning, as the engine has its own kerning

Changed paths:
    engines/nancy/font.cpp
    engines/nancy/font.h


diff --git a/engines/nancy/font.cpp b/engines/nancy/font.cpp
index 13ce11d4ace..2235f9cc837 100644
--- a/engines/nancy/font.cpp
+++ b/engines/nancy/font.cpp
@@ -51,7 +51,7 @@ void Font::read(Common::SeekableReadStream &stream) {
 	_color1CoordsOffset.y = stream.readUint32LE();
 
 	_spaceWidth = stream.readUint16LE();
-	_charSpace = stream.readSint16LE() - 1; // Account for the added pixel in readRect
+	_charSpace = stream.readSint16LE();
 
 	_uppercaseOffset					= stream.readUint16LE();
 	_lowercaseOffset					= stream.readUint16LE();
diff --git a/engines/nancy/font.h b/engines/nancy/font.h
index 9ebab817312..c3d73477efe 100644
--- a/engines/nancy/font.h
+++ b/engines/nancy/font.h
@@ -46,7 +46,7 @@ public:
 	int getFontHeight() const override { return _fontHeight - 1; }
 	int getMaxCharWidth() const override { return _maxCharWidth; }
 	int getCharWidth(uint32 chr) const override;
-	int getKerningOffset(uint32 left, uint32 right) const override { return 1; }
+	int getKerningOffset(uint32 left, uint32 right) const override { return 0; }
 
 	void drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const override;
 


Commit: 1fd05cea96680bd707a00daef1a02f8df480eeb5
    https://github.com/scummvm/scummvm/commit/1fd05cea96680bd707a00daef1a02f8df480eeb5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-02T02:18:16+03:00

Commit Message:
NANCY: More work on cellphone search link records for Nancy10+

Changed paths:
    engines/nancy/action/miscrecords.cpp
    engines/nancy/action/miscrecords.h


diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index b7bab4de98f..1c45bbd347f 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -275,14 +275,12 @@ void AddSearchLink::readData(Common::SeekableReadStream &stream) {
 
 	_extra  = stream.readSint16LE();
 	_flag = stream.readSint16LE();
-	_scene = stream.readSint16LE();
+	_eventFlag = stream.readSint16LE();
 }
 
 void AddSearchLink::execute() {
-	// TODO: finish this
-
-	debug("AddSearchLink: mode=%d, key=%s, value=%s, extra=%d, scene1=%d, scene2=%d", _mode, _key.c_str(), _value.c_str(), _extra, _flag, _scene);
-
+	//NancySceneState.getCellPhonePopup().addSearchLink(
+	//	_mode, _key, _value, _extra, _flag, _eventFlag);
 	finishExecution();
 }
 
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 427bb7e5b1c..b5dd38cc958 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -178,11 +178,11 @@ public:
 	void execute() override;
 
 	int16 _mode = 0;
-	Common::String _key;
-	Common::String _value;
-	int16 _extra = 0;
-	int16 _flag = 0;
-	int16 _scene = 0;
+	Common::String _key;          // CVTX key for the list row text (both modes)
+	Common::String _value;        // body CVTX key (mode 0/email); unused for mode 1
+	int16 _extra = 0;             // page index (mode 1); unused for mode 0
+	int16 _flag = 0;              // stored but unused by the original; reserved
+	int16 _eventFlag = 0;         // event-flag index set when the entry is opened
 
 protected:
 	Common::String getRecordTypeName() const override { return "AddSearchLink"; }




More information about the Scummvm-git-logs mailing list