[Scummvm-git-logs] scummvm master -> 3ba943006aef1c77eab0eb5dde7619cbccaef279
bluegr
noreply at scummvm.org
Sun Jul 19 02:02:08 UTC 2026
This automated email contains information about 10 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
4f18a6819d NANCY: NANCY10: Add a frame cache for Bink panorama videos
7d0c3a21a4 NANCY: NANCY12: Implement new conditional dialog system
2523e0c1e9 NANCY: NANCY10: Don't depress taskbar icons with notification badges
6467156ddd NANCY: Use correct cursor for main menu, setup menu and new game prompt
2b38fde154 NANCY: NANCY10: Add click sound when viewing an item in the inventory
556004256d NANCY: NANCY10: More performance fixes for scrolling
b962792797 NANCY: Copy save name to input box on click
df65b11973 NANCY: Show a confirmation when starting new game while playing a game
ca2a2388e4 NANCY: NANCY10: Fixes for the cellphone browser
3ba943006a NANCY: NANCY10: Fix badge notification when returning from a closeup
Commit: 4f18a6819d5eaca8d8bfe2885cd06c12df6b1203
https://github.com/scummvm/scummvm/commit/4f18a6819d5eaca8d8bfe2885cd06c12df6b1203
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:38+03:00
Commit Message:
NANCY: NANCY10: Add a frame cache for Bink panorama videos
Improves performance when scrubbing left and right in scene panoramas.
Fix #16966
Changed paths:
engines/nancy/movieplayer.cpp
engines/nancy/movieplayer.h
engines/nancy/ui/viewport.cpp
diff --git a/engines/nancy/movieplayer.cpp b/engines/nancy/movieplayer.cpp
index bb9727d5e65..10fe1e814bf 100644
--- a/engines/nancy/movieplayer.cpp
+++ b/engines/nancy/movieplayer.cpp
@@ -36,6 +36,8 @@ MoviePlayer::MoviePlayer() {}
MoviePlayer::~MoviePlayer() {}
bool MoviePlayer::loadFile(const Common::Path &name, bool bidirectionalCache) {
+ freeFrameCache();
+
const Common::Path avfPath = name.append(".avf");
const Common::Path bikPath = name.append(".bik");
@@ -57,6 +59,13 @@ bool MoviePlayer::loadFile(const Common::Path &name, bool bidirectionalCache) {
_decoder.reset();
return false;
}
+
+ // The AVF decoder caches frames itself, so only the Bink path needs ours.
+ _useFrameCache = bidirectionalCache && _videoType == kVideoPlaytypeBink;
+ if (_useFrameCache) {
+ _frameCache.resize(_decoder->getFrameCount());
+ }
+
return true;
}
@@ -66,11 +75,20 @@ bool MoviePlayer::isVideoLoaded() const {
void MoviePlayer::close() {
_currentSurface = nullptr;
+ freeFrameCache();
if (_decoder) {
_decoder->close();
}
}
+void MoviePlayer::freeFrameCache() {
+ for (Graphics::Surface &surf : _frameCache) {
+ surf.free();
+ }
+ _frameCache.clear();
+ _useFrameCache = false;
+}
+
void MoviePlayer::start() { if (_decoder) _decoder->start(); }
void MoviePlayer::stop() { if (_decoder) _decoder->stop(); }
void MoviePlayer::pauseVideo(bool pause) { if (_decoder) _decoder->pauseVideo(pause); }
@@ -119,8 +137,26 @@ const Graphics::Surface *MoviePlayer::decodeNextFrame(int frameNr) {
return frame;
}
- _decoder->seekToFrame(frameNr);
- return _decoder->decodeNextFrame();
+ // Bink path.
+ if (_useFrameCache && (uint)frameNr < _frameCache.size() && _frameCache[frameNr].getPixels()) {
+ return &_frameCache[frameNr];
+ }
+
+ // seekToFrame() re-decodes from the previous keyframe, so only seek when the
+ // frame can't be reached by decoding forward one step.
+ if (_decoder->getCurFrame() + 1 != frameNr) {
+ _decoder->seekToFrame(frameNr);
+ }
+
+ const Graphics::Surface *frame = _decoder->decodeNextFrame();
+
+ // The decoder reuses one surface per frame, so cache a copy.
+ if (_useFrameCache && frame && (uint)frameNr < _frameCache.size()) {
+ _frameCache[frameNr].copyFrom(*frame);
+ return &_frameCache[frameNr];
+ }
+
+ return frame;
}
// --- Simple frame-range player ------------------------------------------
diff --git a/engines/nancy/movieplayer.h b/engines/nancy/movieplayer.h
index 6b32b5b923d..b8413882f92 100644
--- a/engines/nancy/movieplayer.h
+++ b/engines/nancy/movieplayer.h
@@ -22,6 +22,7 @@
#ifndef NANCY_MOVIEPLAYER_H
#define NANCY_MOVIEPLAYER_H
+#include "common/array.h"
#include "common/ptr.h"
#include "common/path.h"
#include "common/rational.h"
@@ -57,7 +58,8 @@ public:
// Load <name> + ".avf"/".bik", auto-detecting the format from which file
// exists (AVF preferred if both do) and creating the matching decoder.
- // bidirectionalCache selects the AVF frame-cache hint (for scrubbing).
+ // bidirectionalCache enables fast bidirectional scrubbing; pass it only for
+ // scrubbed panorama scenes.
bool loadFile(const Common::Path &name, bool bidirectionalCache = false);
bool isVideoLoaded() const;
void close();
@@ -86,8 +88,8 @@ public:
void addFrameTime(uint16 timeToAdd); // AVF only, no-op otherwise
// Decode a frame: frameNr < 0 returns the next frame; otherwise that
- // specific frame via the format-appropriate cached path (AVF decodeFrame +
- // seek; Bink seekToFrame + decodeNextFrame).
+ // specific frame via the format-appropriate cached path (AVF decodeFrame;
+ // Bink decodes forward when possible and caches frames, see _frameCache).
const Graphics::Surface *decodeNextFrame(int frameNr = -1);
// --- Optional simple frame-range player, built on the above. Handy for
@@ -103,10 +105,17 @@ public:
private:
void storeCurrentFrame();
+ void freeFrameCache();
Common::ScopedPtr<Video::VideoDecoder> _decoder;
byte _videoType = kVideoPlaytypeAVF;
+ // Decoded-frame cache for the Bink path (AVF caches internally). Bink seeking
+ // re-decodes from the previous keyframe, so caching keeps panorama scrubbing
+ // fast. Enabled only when loadFile() is asked for a bidirectional cache.
+ bool _useFrameCache = false;
+ Common::Array<Graphics::Surface> _frameCache;
+
// Simple frame-range player state. _currentSurface points at the decoder's
// last-decoded frame (owned by it, valid until the next decode).
const Graphics::Surface *_currentSurface = nullptr;
diff --git a/engines/nancy/ui/viewport.cpp b/engines/nancy/ui/viewport.cpp
index 1a4be6fc8c0..16a76a1819e 100644
--- a/engines/nancy/ui/viewport.cpp
+++ b/engines/nancy/ui/viewport.cpp
@@ -222,9 +222,10 @@ void Viewport::loadVideo(const Common::Path &filename, uint frameNr, uint vertic
_decoder.close();
}
- // The scene video uses the bidirectional AVF frame cache so scrubbing in
- // both directions is fast; the player selects AVF/Bink by which file exists.
- if (!_decoder.loadFile(filename, true)) {
+ // Only panorama scenes step through frames, so only they need the frame cache
+ // for fast bidirectional scrubbing; other scenes would just waste memory.
+ const bool isPanorama = panningType == kPan360 || panningType == kPanLeftRight;
+ if (!_decoder.loadFile(filename, isPanorama)) {
error("Couldn't load video file %s.avf or %s.bik", filename.toString().c_str(), filename.toString().c_str());
}
Commit: 7d0c3a21a47a840f28c5a74ce45b66144bc30279
https://github.com/scummvm/scummvm/commit/7d0c3a21a47a840f28c5a74ce45b66144bc30279
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:39+03:00
Commit Message:
NANCY: NANCY12: Implement new conditional dialog system
Conditional dialog data is now stored in game scenes, instead of being
hardcoded in the executable: 8xx for dialogs and 9xx for goodbyes
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/conversation.cpp
engines/nancy/action/conversation.h
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index b4d518e4925..7bdbc855876 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -196,6 +196,10 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new StartPlayerScrolling();
case 32: // Nancy10
return new UIPopupPrepScene();
+ case 35: // Nancy12
+ return new ConversationInfoCheck();
+ case 36: // Nancy12
+ return new ConversationGoodbye();
case 40:
if (g_nancy->getGameType() <= kGameTypeNancy1)
return new LightningOn(); // Only used in TVD
diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index 03b183e471b..8c1f4889866 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -30,8 +30,10 @@
#include "engines/nancy/util.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/resource.h"
+#include "engines/nancy/iff.h"
#include "engines/nancy/action/conversation.h"
+#include "engines/nancy/action/actionmanager.h"
#include "engines/nancy/action/secondarymovie.h"
#include "engines/nancy/state/scene.h"
@@ -493,9 +495,7 @@ void ConversationSound::execute() {
void ConversationSound::addConditionalDialogue() {
if (g_nancy->getGameType() >= kGameTypeNancy12) {
- // TODO: Nancy12+ moved the per-character conditional dialogue out
- // of the executable and into separate data files.
- warning("Conditional dialogue for character %d is not implemented in Nancy12+", _conditionalResponseCharacterID);
+ addConditionalDialogueNancy12();
return;
}
@@ -570,9 +570,7 @@ void ConversationSound::addConditionalDialogue() {
void ConversationSound::addGoodbye() {
if (g_nancy->getGameType() >= kGameTypeNancy12) {
- // TODO: Nancy12+ moved the per-character goodbye dialogue out
- // of the executable and into separate data files.
- warning("Goodbye dialogue for character %d is not implemented in Nancy12+", _goodbyeResponseCharacterID);
+ addGoodbyeNancy12();
return;
}
@@ -654,6 +652,133 @@ void ConversationSound::addGoodbye() {
NancySceneState.setEventFlag(sceneChange.flagToSet.label, sceneChange.flagToSet.flag);
}
+void ConversationSound::addConditionalDialogueNancy12() {
+ // Every action record in scene S<800 + charID> is one conditional response
+ Common::Path sceneName(Common::String::format("S%u", 800 + _conditionalResponseCharacterID));
+ IFF *iff = g_nancy->_resource->loadIFF(sceneName);
+ if (!iff) {
+ warning("Could not load conditional dialogue scene %s", sceneName.toString().c_str());
+ return;
+ }
+
+ // Response text is in the CONVO file's CVTX chunk, keyed by sound ID
+ const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
+ assert(convo);
+
+ Common::SeekableReadStream *chunk = nullptr;
+ for (uint i = 0; (chunk = iff->getChunkStream("ACT", i)) != nullptr; ++i) {
+ ActionRecord *record = ActionManager::createAndLoadNewRecord(*chunk);
+ delete chunk;
+
+ ConversationInfoCheck *infoCheck = dynamic_cast<ConversationInfoCheck *>(record);
+ if (infoCheck) {
+ NancySceneState.getActionManager().processDependency(infoCheck->_dependencies, *infoCheck, true);
+
+ if (infoCheck->_dependencies.satisfied) {
+ _responses.push_back(ResponseStruct());
+ ResponseStruct &newResponse = _responses.back();
+ newResponse.soundName = infoCheck->_soundID;
+ newResponse.text = convo->texts[infoCheck->_soundID];
+ newResponse.sceneChange.sceneID = infoCheck->_sceneID;
+ newResponse.sceneChange.continueSceneSound = kContinueSceneSound;
+ newResponse.sceneChange.listenerFrontVector.set(0, 0, 1);
+
+ // Drop the response if it's a repeat
+ for (uint j = 0; j < _responses.size() - 1; ++j) {
+ if ( _responses[j].soundName == newResponse.soundName &&
+ _responses[j].text == newResponse.text &&
+ _responses[j].sceneChange.sceneID == newResponse.sceneChange.sceneID) {
+ _responses.pop_back();
+ break;
+ }
+ }
+ }
+ }
+
+ delete record;
+ }
+
+ delete iff;
+}
+
+void ConversationSound::addGoodbyeNancy12() {
+ // Use the first satisfied goodbye record in scene S<900 + charID>
+ Common::Path sceneName(Common::String::format("S%u", 900 + _goodbyeResponseCharacterID));
+ IFF *iff = g_nancy->_resource->loadIFF(sceneName);
+ if (!iff) {
+ warning("Could not load goodbye scene %s", sceneName.toString().c_str());
+ return;
+ }
+
+ // Response text is in the CONVO file's CVTX chunk, keyed by sound ID
+ const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
+ assert(convo);
+
+ bool added = false;
+ Common::SeekableReadStream *chunk = nullptr;
+ for (uint i = 0; !added && (chunk = iff->getChunkStream("ACT", i)) != nullptr; ++i) {
+ ActionRecord *record = ActionManager::createAndLoadNewRecord(*chunk);
+ delete chunk;
+
+ ConversationGoodbye *goodbye = dynamic_cast<ConversationGoodbye *>(record);
+ if (goodbye && !goodbye->_soundIDs.empty() && !goodbye->_sceneIDs.empty()) {
+ NancySceneState.getActionManager().processDependency(goodbye->_dependencies, *goodbye, true);
+
+ if (goodbye->_dependencies.satisfied) {
+ _responses.push_back(ResponseStruct());
+ ResponseStruct &newResponse = _responses.back();
+
+ const Common::String &soundID = goodbye->_soundIDs[g_nancy->_randomSource->getRandomNumber(goodbye->_soundIDs.size() - 1)];
+ newResponse.soundName = soundID;
+ newResponse.text = convo->texts[soundID];
+ newResponse.sceneChange.sceneID = goodbye->_sceneIDs[g_nancy->_randomSource->getRandomNumber(goodbye->_sceneIDs.size() - 1)];
+ newResponse.sceneChange.continueSceneSound = kContinueSceneSound;
+ newResponse.sceneChange.listenerFrontVector.set(0, 0, 1);
+
+ added = true;
+ }
+ }
+
+ delete record;
+ }
+
+ delete iff;
+}
+
+void ConversationInfoCheck::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _soundID);
+ _sceneID = stream.readUint16LE();
+}
+
+void ConversationGoodbye::readData(Common::SeekableReadStream &stream) {
+ if (g_nancy->getGameType() == kGameTypeNancy12) {
+ // A single sound, then a count and a fixed set of 5 candidate scenes
+ _soundIDs.resize(1);
+ readFilename(stream, _soundIDs[0]);
+
+ uint16 numScenes = stream.readUint16LE();
+ _sceneIDs.resize(numScenes);
+ for (uint i = 0; i < 5; ++i) {
+ uint16 sceneID = stream.readUint16LE();
+ if (i < numScenes) {
+ _sceneIDs[i] = sceneID;
+ }
+ }
+
+ return;
+ }
+
+ // Nancy13+: a count-prefixed list of sounds, then one of scenes
+ uint16 numSounds = stream.readUint16LE();
+ readFilenameArray(stream, _soundIDs, numSounds);
+
+ uint16 numScenes = stream.readUint16LE();
+ _sceneIDs.resize(numScenes);
+ for (uint i = 0; i < numScenes; ++i) {
+ _sceneIDs[i] = stream.readUint16LE();
+ }
+}
+
void ConversationSound::ConversationFlag::read(Common::SeekableReadStream &stream) {
type = stream.readByte();
flag.label = stream.readSint16LE();
diff --git a/engines/nancy/action/conversation.h b/engines/nancy/action/conversation.h
index ea33c8f72a7..8d15bda741c 100644
--- a/engines/nancy/action/conversation.h
+++ b/engines/nancy/action/conversation.h
@@ -36,9 +36,8 @@ namespace Action {
// A conversation will auto-advance to a next scene when no responses are available; the next scene
// can either be described within the Conversation data, or can be whatever's pushed onto the scene "stack".
// Also supports branching scenes depending on a condition, though that is only used in older games.
-// Player responses can also be conditional; the original engine had special-purpose "infocheck"
-// functions, two per character ID, which were used to evaluate those conditions. We replace that with
-// the data bundled inside nancy.dat (see devtools/create_nancy).
+// Player responses can also be conditional. Up to Nancy11 the condition data comes from nancy.dat
+// (see devtools/create_nancy); from Nancy12 on it lives in per-character data scenes (S<800 + charID> / S<900 + charID>).
class ConversationSound : public RenderActionRecord {
public:
ConversationSound();
@@ -115,9 +114,11 @@ protected:
void readDataNancy13(Common::SeekableReadStream &stream);
virtual void readCelDataNancy13(Common::SeekableReadStream &stream);
- // Functions for handling the built-in dialogue responses found in the executable
+ // Add conditional/goodbye responses: from nancy.dat up to Nancy11, from data scenes for Nancy12+
void addConditionalDialogue();
void addGoodbye();
+ void addConditionalDialogueNancy12();
+ void addGoodbyeNancy12();
Common::String _text;
@@ -263,6 +264,26 @@ protected:
Common::String getRecordTypeName() const override { return "ConversationCelTerse"; }
};
+// Nancy12+ conditional dialogue record (type 35), one per response, stored in scene S<800 + charID>
+class ConversationInfoCheck : public ActionRecord {
+public:
+ void readData(Common::SeekableReadStream &stream) override;
+ Common::String getRecordTypeName() const override { return "ConversationInfoCheck"; }
+
+ Common::String _soundID; // Doubles as the CONVO text key
+ uint16 _sceneID = 0;
+};
+
+// Nancy12+ goodbye record (type 36), stored in scene S<900 + charID>; one sound and scene are picked at random
+class ConversationGoodbye : public ActionRecord {
+public:
+ void readData(Common::SeekableReadStream &stream) override;
+ Common::String getRecordTypeName() const override { return "ConversationGoodbye"; }
+
+ Common::Array<Common::String> _soundIDs;
+ Common::Array<uint16> _sceneIDs;
+};
+
} // End of namespace Action
} // End of namespace Nancy
Commit: 2523e0c1e9d9c9a793d6197cd3f8f28d95ebb48b
https://github.com/scummvm/scummvm/commit/2523e0c1e9d9c9a793d6197cd3f8f28d95ebb48b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:40+03:00
Commit Message:
NANCY: NANCY10: Don't depress taskbar icons with notification badges
Fix #16971
Changed paths:
engines/nancy/ui/taskbar.cpp
diff --git a/engines/nancy/ui/taskbar.cpp b/engines/nancy/ui/taskbar.cpp
index 4690fa7272e..c3129ecea9d 100644
--- a/engines/nancy/ui/taskbar.cpp
+++ b/engines/nancy/ui/taskbar.cpp
@@ -475,16 +475,22 @@ void Taskbar::handleInput(NancyInput &input) {
return;
}
+ // A badged (highlighted) button never depresses: it keeps showing its
+ // notification sprite through the press and release. Only non-highlighted
+ // buttons swap to the pressed sprite while held.
+ const bool isBadged = restingState(newHovered) == kButtonNotification;
+
if (input.input & NancyInput::kLeftMouseButtonDown) {
// Mouse pressed: show the pressed sprite for the duration of the hold.
- if (_buttonStates[newHovered] != kButtonPressed) {
+ if (!isBadged && _buttonStates[newHovered] != kButtonPressed) {
drawButton(newHovered, kButtonPressed);
}
} 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).
- // Badges are cleared per-view inside the popup, not on click.
- drawButton(newHovered, kButtonHover);
+ // snap the sprite back to its resting hover state (the cursor is still
+ // over it). Badges are cleared per-view inside the popup, not on click,
+ // so a badged button keeps its badge here.
+ drawButton(newHovered, isBadged ? kButtonNotification : kButtonHover);
_clickedButton = newHovered;
playClickSound(newHovered);
Commit: 6467156dddf609f9e0b4c297c2a86dd7f1d024ad
https://github.com/scummvm/scummvm/commit/6467156dddf609f9e0b4c297c2a86dd7f1d024ad
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:40+03:00
Commit Message:
NANCY: Use correct cursor for main menu, setup menu and new game prompt
Fix #16978
Changed paths:
engines/nancy/state/mainmenu.cpp
engines/nancy/state/savedialog.cpp
engines/nancy/state/setupmenu.cpp
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index 79c6778b4ed..3ee58e4f5f9 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -84,7 +84,7 @@ void MainMenu::init() {
_background.init(_menuData->_imageName);
_background.registerGraphics();
- g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
g_nancy->setMouseEnabled(true);
if (!g_nancy->_sound->isSoundPlaying("MSND")) {
@@ -161,7 +161,7 @@ void MainMenu::run() {
}
}
- g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
}
void MainMenu::stop() {
diff --git a/engines/nancy/state/savedialog.cpp b/engines/nancy/state/savedialog.cpp
index f58eea0049b..1ecab823ca7 100644
--- a/engines/nancy/state/savedialog.cpp
+++ b/engines/nancy/state/savedialog.cpp
@@ -55,7 +55,7 @@ void SaveDialog::process() {
break;
}
- g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
}
void SaveDialog::onStateEnter(const NancyState::NancyState prevState) {
diff --git a/engines/nancy/state/setupmenu.cpp b/engines/nancy/state/setupmenu.cpp
index a08788e2f6b..f65455951a7 100644
--- a/engines/nancy/state/setupmenu.cpp
+++ b/engines/nancy/state/setupmenu.cpp
@@ -129,7 +129,7 @@ void SetupMenu::init() {
_background.registerGraphics();
- g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
g_nancy->setMouseEnabled(true);
g_nancy->_sound->stopSound("MSND");
@@ -234,7 +234,7 @@ void SetupMenu::run() {
}
}
- g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
}
void SetupMenu::stop() {
Commit: 2b38fde154ff8eca435c05fa8c5062a9139d1d44
https://github.com/scummvm/scummvm/commit/2b38fde154ff8eca435c05fa8c5062a9139d1d44
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:41+03:00
Commit Message:
NANCY: NANCY10: Add click sound when viewing an item in the inventory
Fix #16982
Changed paths:
engines/nancy/ui/inventorypopup.cpp
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 5c57e45d451..11d19426fe0 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -533,6 +533,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
if (item.keepItem == kInvItemNewSceneView) {
// Close-up view: stash the item and warp to its scene, which
// dismisses the popup. A normal pickup keeps the popup open.
+ g_nancy->_sound->playSound("BUOK");
NancySceneState.pushScene(itemID);
SceneChangeDescription sceneChange;
sceneChange.sceneID = item.sceneID;
Commit: 556004256d62331c6af6139d089f9fba3dba612d
https://github.com/scummvm/scummvm/commit/556004256d62331c6af6139d089f9fba3dba612d
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:42+03:00
Commit Message:
NANCY: NANCY10: More performance fixes for scrolling
Fix #16973
Changed paths:
engines/nancy/ui/conversationpopup.cpp
engines/nancy/ui/inventorypopup.cpp
engines/nancy/ui/notebookpopup.cpp
diff --git a/engines/nancy/ui/conversationpopup.cpp b/engines/nancy/ui/conversationpopup.cpp
index 4660c8071a0..069dbef981b 100644
--- a/engines/nancy/ui/conversationpopup.cpp
+++ b/engines/nancy/ui/conversationpopup.cpp
@@ -236,11 +236,17 @@ void ConversationPopup::handleInput(NancyInput &input) {
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;
-
- drawBackground();
- drawContent();
- drawScrollbar(kUIButtonPressed);
+ const float newScrollPos = travel > 0 ? (float)(clamped - trackLocal.top) / (float)travel : 0.0f;
+
+ // Only re-render when the thumb actually moves. drawContent() re-lays out
+ // the whole text surface, so calling it every frame while the button is
+ // merely held down pins the CPU and makes dragging choppy.
+ if (newScrollPos != _scrollPos) {
+ _scrollPos = newScrollPos;
+ drawBackground();
+ drawContent();
+ drawScrollbar(kUIButtonPressed);
+ }
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 11d19426fe0..16fc372ab15 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -407,9 +407,16 @@ void InventoryPopup::handleInput(NancyInput &input) {
const int newThumbTop = chunkMouse.y - _scrollbarGrabOffset;
const int clamped = CLIP<int>(newThumbTop, track.top, track.top + travel);
- _scrollPos = travel > 0 ? (float)(clamped - track.top) / (float)travel : 0.0f;
- updatePageFromScroll();
- refreshGrid();
+ const float newScrollPos = travel > 0 ? (float)(clamped - track.top) / (float)travel : 0.0f;
+
+ // Only re-render when the thumb actually moves. refreshGrid() redraws the
+ // whole popup, so calling it every frame while the button is merely held
+ // down pins the CPU and makes dragging choppy.
+ if (newScrollPos != _scrollPos) {
+ _scrollPos = newScrollPos;
+ updatePageFromScroll();
+ refreshGrid();
+ }
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index 82f8d215d02..ea5d64dea52 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -296,8 +296,16 @@ void NotebookPopup::handleInput(NancyInput &input) {
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;
- refreshContent();
+ const float newScrollPos = travel > 0 ? (float)(clamped - trackLocal.top) / (float)travel : 0.0f;
+
+ // Only re-render when the thumb actually moves. refreshContent() re-lays
+ // out the whole popup (background, tabs, caption and full text surface),
+ // so calling it every frame while the button is merely held down pins the
+ // CPU and makes dragging choppy.
+ if (newScrollPos != _scrollPos) {
+ _scrollPos = newScrollPos;
+ refreshContent();
+ }
if (input.input & NancyInput::kLeftMouseButtonUp) {
_scrollbarDragging = false;
Commit: b962792797c90f779f3a01f934506f6a82805ab5
https://github.com/scummvm/scummvm/commit/b962792797c90f779f3a01f934506f6a82805ab5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:43+03:00
Commit Message:
NANCY: Copy save name to input box on click
Fix #16983
Changed paths:
engines/nancy/state/loadsave.cpp
diff --git a/engines/nancy/state/loadsave.cpp b/engines/nancy/state/loadsave.cpp
index 8cc51808b76..9bc99bf336f 100644
--- a/engines/nancy/state/loadsave.cpp
+++ b/engines/nancy/state/loadsave.cpp
@@ -1006,6 +1006,13 @@ void LoadSaveMenu_V2::run() {
_loadButton->setDisabled(false);
+ // Copy the clicked save's name into the input textbox at the top,
+ // so clicking Save overwrites that save without retyping its name
+ if (_currentPage == 0 && _inputTextbox && Scene::hasInstance()) {
+ _enteredString = _filenameStrings[i];
+ writeToInputTextbox(_baseFont);
+ }
+
_hoveredSave = -1;
_selectedSave = i;
Commit: df65b119738aaf5ef20b65d27897608c9e5ba228
https://github.com/scummvm/scummvm/commit/df65b119738aaf5ef20b65d27897608c9e5ba228
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:44+03:00
Commit Message:
NANCY: Show a confirmation when starting new game while playing a game
Fix #16979
Changed paths:
engines/nancy/state/mainmenu.cpp
diff --git a/engines/nancy/state/mainmenu.cpp b/engines/nancy/state/mainmenu.cpp
index 3ee58e4f5f9..315c800cb5f 100644
--- a/engines/nancy/state/mainmenu.cpp
+++ b/engines/nancy/state/mainmenu.cpp
@@ -170,14 +170,45 @@ void MainMenu::stop() {
// Credits
g_nancy->setState(NancyState::kCredits);
break;
- case 1:
+ case 1: {
// New Game
+ auto *sdlg = GetEngineData(SDLG);
+
+ if (sdlg && sdlg->dialogs.size() > 2 && Scene::hasInstance() && !g_nancy->_hasJustSaved) {
+ // nancy6+ warns before abandoning an in-progress game. Reuse the
+ // "load a new game without saving the current one" dialog.
+ if (!ConfMan.hasKey("sdlg_return", Common::ConfigManager::kTransientDomain)) {
+ // Request the dialog
+ ConfMan.setInt("sdlg_id", 2, Common::ConfigManager::kTransientDomain);
+ _destroyOnExit = false;
+ g_nancy->setState(NancyState::kSaveDialog);
+ break;
+ } else {
+ // Dialog has returned
+ _destroyOnExit = true;
+ g_nancy->_graphics->suppressNextDraw();
+ uint ret = ConfMan.getInt("sdlg_return", Common::ConfigManager::kTransientDomain);
+ ConfMan.removeKey("sdlg_return", Common::ConfigManager::kTransientDomain);
+
+ if (ret != 0) {
+ // "No" or "Cancel" keeps us in the main menu
+ _selected = -1;
+ clearButtonState();
+ _state = kRun;
+ break;
+ }
+
+ // "Yes" falls through to start a new game
+ }
+ }
+
if (Scene::hasInstance()) {
NancySceneState.destroy(); // Destroy the existing Scene and create a new one
}
g_nancy->setState(NancyState::kScene);
break;
+ }
case 2:
// Load and Save Game, TODO
g_nancy->setState(NancyState::kLoadSave);
Commit: ca2a2388e4c3cfd6c5b25dce60cf8789ac5d5fd1
https://github.com/scummvm/scummvm/commit/ca2a2388e4c3cfd6c5b25dce60cf8789ac5d5fd1
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:45+03:00
Commit Message:
NANCY: NANCY10: Fixes for the cellphone browser
Fix the header, back button and rendering of long pages
Fix #16985, #16986
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/cellphonepopup.h
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 7f93ea15f7a..bc95aa4a8d9 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -519,14 +519,18 @@ void CellPhonePopup::drawScreenContent() {
break;
case kWaitPickup:
- case kConnected:
- drawConnectedLabel();
+ // Still ringing â only the connecting sprite, plus the Back button.
drawConnectingSprite();
if (isCallBackButtonActive()) {
drawBackButton(0);
}
break;
+ case kConnected:
+ drawConnectedLabel();
+ drawConnectingSprite();
+ break;
+
case kInvalidNumber:
case kWaitInvalid:
drawConnectingSprite();
@@ -567,21 +571,16 @@ void CellPhonePopup::drawScreenContent() {
break;
case kContentView:
- // Browser pages use the interactive top-row "SEARCH" button
- // (subButtons[8], drawn below) in place of a static heading; help /
- // email keep their static heading.
- if (_contentHeading && _contentHeading != &_uiclData->browserHeading) {
+ // Articles show the BROWSER heading; the main browser page shows the
+ // interactive SEARCH button (drawn below) instead.
+ if (_contentHeading &&
+ (_contentHeading != &_uiclData->browserHeading || isBrowserArticle())) {
drawHeading(*_contentHeading);
}
drawContentView();
- // Help's Back sits in the lower ribbon (subButtons[0]); the zoomed
- // articles' Back sits at the bottom of the screen (subButtons[7]).
- // drawDirectoryArrows() blits whichever scroll pair applies.
drawDirectoryArrows();
- drawBackButton(isHelpContentView() ? 0 : 7);
- // Browser pages carry the top-row "SEARCH" button (subButtons[8]),
- // which highlights green on hover and opens the search-topics list.
- if (_contentHeading == &_uiclData->browserHeading) {
+ drawBackButton(contentViewBottomButton());
+ if (_contentHeading == &_uiclData->browserHeading && !isBrowserArticle()) {
drawHubButton(8);
}
break;
@@ -882,6 +881,9 @@ void CellPhonePopup::openBrowserHome() {
// further pages / the search list.
const UIBW *browserData = GetEngineData(UIBW);
if (browserData && !browserData->pages.empty()) {
+ // Remember the main-page key so isBrowserArticle() can tell it apart.
+ _browserHomeKey = browserData->pages[0].imageName.toString();
+ _browserHomeKey.toUppercase();
openContentView(browserData->pages[0].imageName.toString(), _uiclData->browserHeading);
// The homepage's Back button always returns to the main phone (welcome)
// screen, regardless of whether the browser was opened from the online
@@ -949,11 +951,18 @@ void CellPhonePopup::renderContentPage(int surfaceWidth) {
}
}
const uint32 trans = g_nancy->_graphics->getTransColor();
- ht.render(surfaceWidth, 2000, trans, renderText, _uiclData->fontId2);
- _contentCacheSurface.copyFrom(ht.surface());
+ // Render into a tall scratch surface, then cache only the used height
+ static const uint16 kMaxContentHeight = 6000;
+ ht.render(surfaceWidth, kMaxContentHeight, trans, renderText, _uiclData->fontId2);
+
+ const uint16 textHeight = MIN<uint16>(MAX<uint16>(ht.textHeight(), 1), kMaxContentHeight);
+ _contentCacheSurface.create(surfaceWidth, textHeight, ht.surface().format);
_contentCacheSurface.setTransparentColor(trans);
- _contentCacheTextHeight = ht.textHeight();
+ _contentCacheSurface.clear(trans);
+ _contentCacheSurface.blitFrom(ht.surface(), Common::Rect(0, 0, (int16)surfaceWidth, (int16)textHeight),
+ Common::Point(0, 0));
+ _contentCacheTextHeight = textHeight;
_contentCacheHotspots = ht.hotspots();
}
@@ -1177,12 +1186,28 @@ int CellPhonePopup::currentBackButtonIndex() const {
case kEmailList:
return 7;
case kContentView:
- return isHelpContentView() ? 0 : 7;
+ return (int)contentViewBottomButton();
default:
return -1;
}
}
+uint CellPhonePopup::contentViewBottomButton() const {
+ // Help Back (0); browser-article HOME (9); main page / email article Back (7).
+ if (isHelpContentView()) {
+ return 0;
+ }
+ return isBrowserArticle() ? 9 : 7;
+}
+
+bool CellPhonePopup::isBrowserArticle() const {
+ // A browser content view other than the main page opened by openBrowserHome().
+ if (_contentHeading != &_uiclData->browserHeading) {
+ return false;
+ }
+ return !_browserHomeKey.empty() && !_contentKey.equalsIgnoreCase(_browserHomeKey);
+}
+
const UICL::ThreeRectWidget &CellPhonePopup::scrollUpButton() const {
// Directory and help both scroll with the small-LCD arrow pair
// (subButtons[1]/[2]); the zoomed email / browser articles use [5]/[6].
@@ -2091,9 +2116,9 @@ void CellPhonePopup::handleInput(NancyInput &input) {
const Common::Point popupMouseLink(chunkMouse.x - _screenPosition.left,
chunkMouse.y - _screenPosition.top);
- // On browser pages the top-row "SEARCH" button (subButtons[8]) opens the
- // search-topics list and highlights green while hovered.
- if (_contentHeading == &_uiclData->browserHeading &&
+ // The main browser page carries the top-row SEARCH button (subButtons[8])
+ // which opens the search list; it highlights green while hovered.
+ if (_contentHeading == &_uiclData->browserHeading && !isBrowserArticle() &&
!_uiclData->subButtons[8].destRect.isEmpty()) {
Common::Rect searchBtn = _uiclData->subButtons[8].destRect;
searchBtn.translate(-_screenPosition.left, -_screenPosition.top);
@@ -2163,11 +2188,9 @@ void CellPhonePopup::handleInput(NancyInput &input) {
const bool overUpDown =
upDst.contains(chunkMouse) || downDst.contains(chunkMouse);
- // Help draws its Back button in the lower ribbon (subButtons[0]); the
- // zoomed email / browser view draws it at the bottom of the screen
- // (subButtons[7]). Hit-test the matching button so it lines up with the
- // visible sprite.
- const Common::Rect backHit = backButtonHitRect(isHelpContentView() ? 0 : 7);
+ // Hit-test the bottom button that this view actually draws.
+ const bool onBrowserArticle = isBrowserArticle();
+ const Common::Rect backHit = backButtonHitRect(contentViewBottomButton());
const Common::Point popupMouse(chunkMouse.x - _screenPosition.left,
chunkMouse.y - _screenPosition.top);
if (!overUpDown && !backHit.isEmpty() && backHit.contains(popupMouse)) {
@@ -2175,7 +2198,12 @@ void CellPhonePopup::handleInput(NancyInput &input) {
if (input.input & NancyInput::kLeftMouseButtonUp) {
_contentKey.clear();
_contentHeading = nullptr;
- enterScreenState(_contentReturnState);
+ if (onBrowserArticle) {
+ // HOME â the browser homepage (River Heights Wireless).
+ openBrowserHome();
+ } else {
+ enterScreenState(_contentReturnState);
+ }
input.eatMouseInput();
return;
}
diff --git a/engines/nancy/ui/cellphonepopup.h b/engines/nancy/ui/cellphonepopup.h
index 3cc25bda5da..4a7ba6d5130 100644
--- a/engines/nancy/ui/cellphonepopup.h
+++ b/engines/nancy/ui/cellphonepopup.h
@@ -233,6 +233,10 @@ private:
// subButtons index of the Back / HOME button visible in the current state,
// or -1 when none is shown. Used to drive its hover highlight.
int currentBackButtonIndex() const;
+ // subButtons index of the bottom button on a content view.
+ uint contentViewBottomButton() const;
+ // A browser content view other than the main page (i.e. an actual web page).
+ bool isBrowserArticle() const;
// Move the directory selection by delta, scrolling as needed.
void moveDirectorySelection(int delta);
@@ -288,6 +292,8 @@ private:
ScreenState _contentReturnState = kOnlineHub;
const UICL::SrcDestRectPair *_contentHeading = nullptr;
Common::String _contentKey;
+ // Uppercased key of the main browser page (empty until the browser is opened).
+ Common::String _browserHomeKey;
uint _contentScroll = 0;
// Email "opening" transition: the visible row whose closed envelope briefly
Commit: 3ba943006aef1c77eab0eb5dde7619cbccaef279
https://github.com/scummvm/scummvm/commit/3ba943006aef1c77eab0eb5dde7619cbccaef279
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-19T05:01:45+03:00
Commit Message:
NANCY: NANCY10: Fix badge notification when returning from a closeup
Fix #16981
Changed paths:
engines/nancy/state/scene.cpp
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index a15f95cffe3..6ea15e351c6 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -286,6 +286,12 @@ void Scene::popScene(bool inventory) {
changeScene(_sceneState.pushedInvScene);
_sceneState.isInvScenePushed = false;
addItemToInventory(_sceneState.pushedInvItemID);
+ // Returning from a close-up view restores an item the player already
+ // owned, so it must not raise the "new item" taskbar badge that
+ // addItemToInventory sets for a genuine pickup.
+ if (_taskbar) {
+ _taskbar->clearNotification(kTaskButtonInventory, 0);
+ }
_sceneState.pushedInvItemID = kEvNoEvent;
_sceneState.pushedInvScene.sceneID = kNoScene;
}
More information about the Scummvm-git-logs
mailing list