[Scummvm-git-logs] scummvm master -> 63b12c3149e39e6709de9a90342f1f3f0ca92efa

bluegr noreply at scummvm.org
Tue Jul 21 23:46:34 UTC 2026


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

Summary:
54608cee24 NANCY: NANCY10: Read the frame image name from the TASK chunk
54b087a8fc NANCY: NANCY13: Show subtitles for Nancy's messages
8d46b754bd NANCY: NANCY12: Add detection entry for the French version
4d66c4f005 NANCY: Filter invalid file name characters when dumping action records
1da8ef9083 NANCY: NANCY12: Implement new functionality in CollisionPuzzle/TileMove
9a2b2685fe NANCY: NANCY10: Fix display of mini vs full overlay in ScrollTextBox
7df1a23fbb NANCY: Fix a bug with cell width calculation in CollisionPuzzle
63b12c3149 NANCY: NANCY12: More work on the ResourceUse AR


Commit: 54608cee24a8495a8c79392d8308585c60953f6a
    https://github.com/scummvm/scummvm/commit/54608cee24a8495a8c79392d8308585c60953f6a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:13+03:00

Commit Message:
NANCY: NANCY10: Read the frame image name from the TASK chunk

Fixes the frame image in Nancy13 (where it was renamed)

Changed paths:
    engines/nancy/state/scene.cpp


diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 9e66d491184..3ed6fcee6d3 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1641,12 +1641,16 @@ void Scene::initStaticData() {
 	auto *bootSummary = GetEngineData(BSUM);
 	assert(bootSummary);
 
-	Common::Path imageName = "FRAME";
+	Common::Path imageName;
 
 	if (g_nancy->getGameType() <= kGameTypeNancy9) {
 		const ImageChunk *fr0 = (const ImageChunk *)g_nancy->getEngineData("FR0");
 		assert(fr0);
 		imageName = fr0->imageName;
+	} else {
+		auto *taskData = GetEngineData(TASK);
+		assert(taskData);
+		imageName = taskData->imageName;
 	}
 
 	auto *mapData = GetEngineData(MAP);


Commit: 54b087a8fce98eaa620512b8aa34823be753797b
    https://github.com/scummvm/scummvm/commit/54b087a8fce98eaa620512b8aa34823be753797b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:14+03:00

Commit Message:
NANCY: NANCY13: Show subtitles for Nancy's messages

In Nancy 13, most ScrollTextBox subtitles were missing (chiefly Nancy's
narration, observations, and tutorial lines); conversation subtitles
were fine as they render through a separate ConversationPopup widget.
Nancy13 dropped the explicit closed-caption record (PlaySoundCC) used
by earlier games: instead, when any sound plays, the engine looks its
name up in the CVTX text chunks and shows any match in the game
textbox as a subtitle.

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


diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 1b0609006ed..1814f86a04d 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -48,6 +48,31 @@ static uint selectRandomSound(Common::Array<Common::String> &soundNames) {
 	return g_nancy->_randomSource->getRandomNumber(soundNames.size() - 1);
 }
 
+// Nancy13+ subtitles are no longer stored inside the sound record. Instead, the
+// engine looks the played sound's name up in the CVTX text chunks when the sound
+// starts and, if a matching entry exists, shows it in the game textbox. The
+// autotext chunk (narration/observations) is searched first, then the convo chunk.
+static Common::String resolveSoundSubtitle(const Common::String &soundName) {
+	if (soundName.empty() || soundName.equalsIgnoreCase("NO SOUND")) {
+		return Common::String();
+	}
+
+	const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
+	if (autotext) {
+		Common::String text = autotext->texts.getValOrDefault(soundName, "");
+		if (!text.empty()) {
+			return text;
+		}
+	}
+
+	const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
+	if (convo) {
+		return convo->texts.getValOrDefault(soundName, "");
+	}
+
+	return Common::String();
+}
+
 void SetVolume::readData(Common::SeekableReadStream &stream) {
 	channel = stream.readUint16LE();
 	volume = stream.readByte();
@@ -163,6 +188,9 @@ void PlaySound::readDataNancy13(Common::SeekableReadStream &stream) {
 		_sound.volume = stream.readUint16LE();
 
 		_sound.name = names[selectRandomSound(names)];
+
+		// Subtitles are keyed by the played sound's name in the CVTX chunks.
+		_ccText = resolveSoundSubtitle(_sound.name);
 	}
 
 	// No inline SoundEffectDescription anymore, and the scene change is just a
@@ -186,6 +214,14 @@ void PlaySound::execute() {
 		g_nancy->_sound->loadSound(_sound, &_soundEffect);
 		g_nancy->_sound->playSound(_sound);
 
+		// Nancy13+ shows the sound's subtitle (resolved from its name) in the
+		// game textbox. Earlier games use the explicit PlaySoundCC records instead.
+		if (g_nancy->getGameType() >= kGameTypeNancy13 && !_ccText.empty() &&
+				ConfMan.getBool("subtitles", ConfMan.getActiveDomainName())) {
+			NancySceneState.getTextbox().clear();
+			NancySceneState.getTextbox().addTextLine(_ccText);
+		}
+
 		if (g_nancy->getGameType() >= kGameTypeNancy13) {
 			// Nancy13 sets a list of event flags.
 			for (const FlagDescription &flag : _flags) {
@@ -257,7 +293,9 @@ void PlaySoundCC::readData(Common::SeekableReadStream &stream) {
 }
 
 void PlaySoundCC::execute() {
-	if (_state == kBegin && _ccText.size() && ConfMan.getBool("subtitles", ConfMan.getActiveDomainName())) {
+	// Nancy13+ resolves the subtitle from the sound name in PlaySound::execute.
+	if (g_nancy->getGameType() < kGameTypeNancy13 &&
+			_state == kBegin && _ccText.size() && ConfMan.getBool("subtitles", ConfMan.getActiveDomainName())) {
 		NancySceneState.getTextbox().clear();
 		NancySceneState.getTextbox().addTextLine(_ccText);
 	}
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
index ef60b81c1d2..bf69d95bd39 100644
--- a/engines/nancy/action/soundrecords.h
+++ b/engines/nancy/action/soundrecords.h
@@ -114,6 +114,11 @@ public:
 	Common::Array<FlagDescription> _flags;
 	byte _afterSoundAction = 0;	// Nancy13+: 1 dismisses the text box overlay
 
+	// Subtitle shown in the game textbox while the sound plays. In Nancy13+ this
+	// is resolved from the sound name (see readDataNancy13); earlier games store
+	// it explicitly in the closed-caption records below.
+	Common::String _ccText;
+
 	Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
 
 protected:
@@ -132,8 +137,6 @@ public:
 
 	void readCCText(Common::SeekableReadStream &stream, Common::String &out);
 
-	Common::String _ccText;
-
 protected:
 	Common::String getRecordTypeName() const override;
 };


Commit: 8d46b754bdca54f6323333d6c2975ff75e53086b
    https://github.com/scummvm/scummvm/commit/8d46b754bdca54f6323333d6c2975ff75e53086b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:15+03:00

Commit Message:
NANCY: NANCY12: Add detection entry for the French version

Fix #17000

Changed paths:
    engines/nancy/detection_tables.h


diff --git a/engines/nancy/detection_tables.h b/engines/nancy/detection_tables.h
index 013840abf84..82462e15bfb 100644
--- a/engines/nancy/detection_tables.h
+++ b/engines/nancy/detection_tables.h
@@ -803,6 +803,17 @@ static const NancyGameDescription gameDescriptions[] = {
 		},
 		kGameTypeNancy12
 	},
+	{ // MD5 by tunnelsociety from bug #17000
+		{
+			"nancy12", nullptr,
+			AD_ENTRY1s("ciftree.dat", "816db22b5c4d5211336b742fee8ea080", 45822011),
+			Common::FR_FRA,
+			Common::kPlatformWindows,
+			ADGF_UNSTABLE | ADGF_DROPPLATFORM,
+			NANCY8_GUIOPTIONS
+		},
+		kGameTypeNancy12
+	},
 	{ // MD5 by bluegr
 		{
 			"nancy13", nullptr,


Commit: 4d66c4f005a47109019cf999fdeb0b5907db1ae7
    https://github.com/scummvm/scummvm/commit/4d66c4f005a47109019cf999fdeb0b5907db1ae7
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:16+03:00

Commit Message:
NANCY: Filter invalid file name characters when dumping action records

Changed paths:
    engines/nancy/console.cpp


diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index ce75446f40d..9f076c414e2 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -704,11 +704,14 @@ bool NancyConsole::Cmd_actionRecordExport(int argc, const char **argv) {
 		char descBuf[48];
 		chunk->read(descBuf, 48);
 		descBuf[47] = '\0';
+		Common::String desc(descBuf);
+		desc.replace('/', '-');
+		desc.replace('\\', '-');
 		byte ARType = chunk->readByte();
 		chunk->skip(1); // execType
 
 		Common::DumpFile f;
-		Common::String filename = Common::String::format("%s_ar_%d_scene_%d_%d_%s.dat", g_nancy->getGameId(), ARType, sceneId, recordId, descBuf);
+		Common::String filename = Common::String::format("%s_ar_%d_scene_%d_%d_%s.dat", g_nancy->getGameId(), ARType, sceneId, recordId, desc.c_str());
 		f.open(Common::Path(filename));
 		f.writeStream(chunk, chunk->size() - 50);
 		f.close();


Commit: 1da8ef908320a4cf352fe060a2a5d20ed23fc729
    https://github.com/scummvm/scummvm/commit/1da8ef908320a4cf352fe060a2a5d20ed23fc729
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:17+03:00

Commit Message:
NANCY: NANCY12: Implement new functionality in CollisionPuzzle/TileMove

Fixes the Parakeet tile move puzzle

Changed paths:
    engines/nancy/action/puzzle/collisionpuzzle.cpp
    engines/nancy/action/puzzle/collisionpuzzle.h


diff --git a/engines/nancy/action/puzzle/collisionpuzzle.cpp b/engines/nancy/action/puzzle/collisionpuzzle.cpp
index 41b918aa207..15f6a7e4d16 100644
--- a/engines/nancy/action/puzzle/collisionpuzzle.cpp
+++ b/engines/nancy/action/puzzle/collisionpuzzle.cpp
@@ -91,15 +91,21 @@ void CollisionPuzzle::init() {
 					continue;
 				}
 
+				// Start the solve piece in the slot two cells left of the grid
+				int pieceX = (int)x;
+				if (id == 6 && _tileMoveSolveStartsInSlot) {
+					pieceX -= 2;
+				}
+
 				newPiece._drawSurface.create(_image, _pieceSrcs[id - 1]);
-				Common::Rect pos = getScreenPosition(Common::Point(x, y));
+				Common::Rect pos = getScreenPosition(Common::Point(pieceX, y));
 				if (_lineWidth == 6) {
 					pos.translate(-1, 0); // Improvement
 				}
 				pos.setWidth(newPiece._drawSurface.w);
 				pos.setHeight(newPiece._drawSurface.h);
 				newPiece.moveTo(pos);
-				newPiece._gridPos = Common::Point(x, y);
+				newPiece._gridPos = Common::Point(pieceX, y);
 				newPiece.setVisible(true);
 				newPiece.setTransparent(true);
 
@@ -210,6 +216,11 @@ void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
 		_tileMoveExitPos.x = stream.readUint16LE();
 		_tileMoveExitIndex = stream.readUint16LE();
 		numPieces = 6;
+
+		// Nancy 12: when set, the solve piece starts in the slot left of the grid
+		if (g_nancy->getGameType() >= kGameTypeNancy12) {
+			_tileMoveSolveStartsInSlot = stream.readByte() == 1;
+		}
 	}
 
 	// Nancy 12 enlarged the Collision grid from 8x8 to 12x12; TileMove went 8 to 11 in Nancy 10
@@ -364,6 +375,12 @@ void CollisionPuzzle::execute() {
 			int16 w = g_nancy->getGameType() <= kGameTypeNancy9 ? _grid.size() : _grid[0].size();
 			int16 h = g_nancy->getGameType() <= kGameTypeNancy9 ? _grid[0].size() : _grid.size();
 			Common::Rect gridRect(w, h);
+
+			// The left start slot is outside the grid too, but isn't the exit
+			if (_tileMoveSolveStartsInSlot && pos.x < 0) {
+				return;
+			}
+
 			if (!posRect.contains(_tileMoveExitPos) && gridRect.contains(pos)) {
 				return;
 			}
diff --git a/engines/nancy/action/puzzle/collisionpuzzle.h b/engines/nancy/action/puzzle/collisionpuzzle.h
index de69f379c50..c92068b7188 100644
--- a/engines/nancy/action/puzzle/collisionpuzzle.h
+++ b/engines/nancy/action/puzzle/collisionpuzzle.h
@@ -90,6 +90,7 @@ protected:
 
 	Common::Point _tileMoveExitPos = Common::Point(-1, -1);
 	uint _tileMoveExitIndex = 0;
+	bool _tileMoveSolveStartsInSlot = false;
 
 	bool _usesExitButton = false;
 	Common::Rect _exitButtonSrc;


Commit: 9a2b2685feeac063e8ec9b00638f300ce85a8a67
    https://github.com/scummvm/scummvm/commit/9a2b2685feeac063e8ec9b00638f300ce85a8a67
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:19+03:00

Commit Message:
NANCY: NANCY10: Fix display of mini vs full overlay in ScrollTextBox

Fix #16888

Changed paths:
    engines/nancy/ui/scrolltextbox.cpp
    engines/nancy/ui/scrolltextbox.h


diff --git a/engines/nancy/ui/scrolltextbox.cpp b/engines/nancy/ui/scrolltextbox.cpp
index 1a46e4cd0dd..9a858580149 100644
--- a/engines/nancy/ui/scrolltextbox.cpp
+++ b/engines/nancy/ui/scrolltextbox.cpp
@@ -141,14 +141,13 @@ void ScrollTextBox::drawContent() {
 	const uint16 fontID = _fontIDOverride != -1 ? (uint16)_fontIDOverride : _tboxData->highlightConversationFontID;
 	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.
+	// Mini strip vs full overlay with scrollbar, decided by whether the text
+	// overflows the viewport (not by line count): a caption that fits stays mini.
 	const Common::Rect viewport = textViewportLocal();
 	const int fullHeight = _fullPopupRect.height();
 	const int contentHeight = getInnerHeight();
 
-	_expanded = _numDrawnLines > 2;
+	_expanded = contentHeight > viewport.height();
 
 	Common::Rect boxRect;
 	int visibleTextHeight;
@@ -161,8 +160,8 @@ void ScrollTextBox::drawContent() {
 			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.
+		// A strip at the top of the overlay, sized to the text with a two-line
+		// minimum. Drop the chrome's bottom margin so it hugs the content.
 		const Font *font = g_nancy->_graphics->getFont(fontID);
 		const int lineStep = font->getLineHeight() + font->getLineHeight() / 9;
 		const int twoLineContent = _tboxData->scrollbarDefaultPos.y + 2 * lineStep;
diff --git a/engines/nancy/ui/scrolltextbox.h b/engines/nancy/ui/scrolltextbox.h
index ffffb4d8e5d..9b798014a1b 100644
--- a/engines/nancy/ui/scrolltextbox.h
+++ b/engines/nancy/ui/scrolltextbox.h
@@ -95,8 +95,8 @@ private:
 	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.
+	// True when text overflows the viewport: full overlay with scrollbar.
+	// False when it fits: mini strip above the taskbar, sized to content.
 	bool _expanded;
 
 	int _fontIDOverride;


Commit: 7df1a23fbba5ff504dfad1e8fe40ff0fe6040f63
    https://github.com/scummvm/scummvm/commit/7df1a23fbba5ff504dfad1e8fe40ff0fe6040f63
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:21+03:00

Commit Message:
NANCY: Fix a bug with cell width calculation in CollisionPuzzle

Fixes pieces overflowing off the grid

Changed paths:
    engines/nancy/action/puzzle/collisionpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/collisionpuzzle.cpp b/engines/nancy/action/puzzle/collisionpuzzle.cpp
index 15f6a7e4d16..a030bce221a 100644
--- a/engines/nancy/action/puzzle/collisionpuzzle.cpp
+++ b/engines/nancy/action/puzzle/collisionpuzzle.cpp
@@ -551,7 +551,8 @@ Common::Rect CollisionPuzzle::getScreenPosition(Common::Point gridPos) {
 	dest.bottom -= 1;
 
 	if (_puzzleType == kTileMove) {
-		dest.setWidth(dest.width() / 2);
+		// The cell width is that of a 1-wide piece; halving the 2-wide sprite rounds up
+		dest.setWidth(_pieceSrcs[1].width() - 1);
 	}
 
 	dest.moveTo(_gridPos);


Commit: 63b12c3149e39e6709de9a90342f1f3f0ca92efa
    https://github.com/scummvm/scummvm/commit/63b12c3149e39e6709de9a90342f1f3f0ca92efa
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-22T02:46:22+03:00

Commit Message:
NANCY: NANCY12: More work on the ResourceUse AR

Implements the payment screens and payment system in Nancy12

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 8b5f3fc3920..4aac737c170 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -22,6 +22,10 @@
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/sound.h"
 #include "engines/nancy/resource.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/font.h"
+#include "engines/nancy/cursor.h"
+#include "engines/nancy/input.h"
 #include "engines/nancy/util.h"
 
 #include "engines/nancy/action/miscrecords.h"
@@ -741,33 +745,165 @@ void HintSystem::selectHint() {
 }
 
 void ResourceUse::readData(Common::SeekableReadStream &stream) {
-	_resourceIndex = stream.readSint16LE();   // which UIRC resource to change
-	_amount = stream.readSint16LE();           // value / delta
-	_mode = stream.readByte();                 // 0 = set, non-zero = add
-	_flag.label = stream.readSint16LE();       // event flag set on success
+	_resourceIndex = stream.readSint16LE();      // which UIRC resource to change
+	_amount = stream.readSint16LE();             // value / delta
+	_mode = stream.readByte();                   // 0 = set, non-zero = add
+	_flag.label = stream.readSint16LE();         // event flag set on success
 	_flag.flag = stream.readByte();
-	// The rest of the 113-byte block (success/fail sounds, an optional scene
-	// change at +0x5b, and a transient "UIResource_Overlay" sprite) isn't
-	// handled yet.
-	stream.skip(0x71 - 8);
+
+	readFilename(stream, _failSoundName);        // played when the change can't be applied
+	readFilename(stream, _successSoundName);     // played when it is applied
+
+	readRect(stream, _paymentHotspot);
+	_useResourceCursor = stream.readByte();
+
+	_sceneID = stream.readUint16LE();
+	_continueSceneSound = stream.readUint16LE();
+
+	_drawResourceOverlay = stream.readByte() != 0;
+	_overlayDest.x = stream.readSint32LE();
+	_overlayDest.y = stream.readSint32LE();
+
+	_drawResourceValue = stream.readByte() != 0;
+	_valueDest.x = stream.readSint32LE();
+	_valueDest.y = stream.readSint32LE();
 }
 
-void ResourceUse::execute() {
+void ResourceUse::init() {
+	Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
+	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
+	_drawSurface.clear(g_nancy->_graphics->getTransColor());
+	setTransparent(true);
+	moveTo(screenBounds);
+
+	const UIRC *uirc = GetEngineData(UIRC);
+	const bool haveItem = uirc && _resourceIndex >= 0 && (uint)_resourceIndex < uirc->items.size();
+
+	if (haveItem && _drawResourceOverlay) {
+		const UIRC::ItemRecord &item = uirc->items[_resourceIndex];
+		Graphics::ManagedSurface image;
+		g_nancy->_resource->loadImage(item.overlayName, image);
+		image.setTransparentColor(_drawSurface.getTransparentColor());
+
+		Common::Rect src = item.rect;
+		Common::Rect dest(_overlayDest.x, _overlayDest.y,
+			_overlayDest.x + src.width(), _overlayDest.y + src.height());
+		_drawSurface.blitFrom(image, src, dest);
+	}
+
+	if (haveItem && _drawResourceValue) {
+		// The value is rendered with a '$' prefix and `unknown2` decimal places
+		// (Old Clock tracks cents), using the font selected by `unknown1`.
+		const UIRC::ItemRecord &item = uirc->items[_resourceIndex];
+		const Font *font = g_nancy->_graphics->getFont(item.unknown1);
+		if (font && item.unknown2 > 0) {
+			const int32 value = NancySceneState.getUIResource(_resourceIndex);
+			const Common::String text = Common::String::format("$%d.%02d", value / 100, value % 100);
+			font->drawString(&_drawSurface, text, _valueDest.x, _valueDest.y, screenBounds.width() - _valueDest.x, 0);
+		}
+	}
+
+	setVisible(_drawResourceOverlay || _drawResourceValue);
+	registerGraphics();
+}
+
+void ResourceUse::applyChange() {
 	if (_mode == 0) {
 		// Set the resource outright.
 		NancySceneState.setUIResource(_resourceIndex, _amount);
 		NancySceneState.setEventFlag(_flag);
+		_paymentApplied = true;
 	} else {
 		// Add the (signed) amount, but never let the resource go negative —
 		// the original skips the change (e.g. when Nancy can't afford it).
 		const int32 result = NancySceneState.getUIResource(_resourceIndex) + _amount;
-		if (result >= 0) {
+		_paymentApplied = result >= 0;
+		if (_paymentApplied) {
 			NancySceneState.setUIResource(_resourceIndex, result);
 			NancySceneState.setEventFlag(_flag);
 		}
 	}
 
-	_isDone = true;
+	// Start the sound matching the outcome, if there is one.
+	const Common::String &soundName = _paymentApplied ? _successSoundName : _failSoundName;
+	if (!soundName.empty() && !soundName.equalsIgnoreCase("NO SOUND") && !soundName.equalsIgnoreCase("NO SOUND PLAY")) {
+		_sound.name = soundName;
+		_sound.numLoops = 1;
+
+		const UIRC *uirc = GetEngineData(UIRC);
+		if (uirc && _resourceIndex >= 0 && (uint)_resourceIndex < uirc->items.size()) {
+			_sound.channelID = uirc->items[_resourceIndex].soundChannel;
+			_sound.volume = uirc->items[_resourceIndex].soundVolume;
+		}
+
+		g_nancy->_sound->loadSound(_sound);
+		g_nancy->_sound->playSound(_sound);
+		_hasSound = true;
+	}
+
+	_paymentResolved = true;
+}
+
+void ResourceUse::handleInput(NancyInput &input) {
+	if (!_interactive || _state != kRun || _paymentResolved) {
+		return;
+	}
+
+	if (NancySceneState.getViewport().convertViewportToScreen(_paymentHotspot).contains(input.mousePos)) {
+		g_nancy->_cursor->setCursorType(_useResourceCursor ?
+			(CursorManager::CursorType)(_resourceIndex + 0x1f) : CursorManager::kNormal, true);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			applyChange();
+		}
+	}
+}
+
+void ResourceUse::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+
+		// A non-degenerate hotspot means the player has to click it (e.g. a coin
+		// slot) to trigger the change; otherwise it happens immediately.
+		_interactive = _paymentHotspot.top != _paymentHotspot.bottom;
+		if (!_interactive) {
+			applyChange();
+		}
+
+		_state = kRun;
+		break;
+	case kRun:
+		// Interactive changes wait for the player to click the hotspot.
+		if (_interactive && !_paymentResolved) {
+			return;
+		}
+
+		// Keep the overlay up until the outcome sound has finished.
+		if (_hasSound && g_nancy->_sound->isSoundPlaying(_sound)) {
+			return;
+		}
+
+		_state = kActionTrigger;
+		break;
+	case kActionTrigger:
+		if (_hasSound) {
+			g_nancy->_sound->stopSound(_sound);
+		}
+
+		setVisible(false);
+
+		// Only a successful change advances the scene.
+		if (_paymentApplied && _sceneID != kNoScene) {
+			SceneChangeDescription sceneChange;
+			sceneChange.sceneID = _sceneID;
+			sceneChange.continueSceneSound = _continueSceneSound;
+			NancySceneState.changeScene(sceneChange);
+		}
+
+		finishExecution();
+		break;
+	}
 }
 
 } // End of namespace Action
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 6391919dad2..9dfeac38e59 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -443,19 +443,53 @@ protected:
 };
 
 // Added in Nancy12 (AR 132). Adjusts a UI overlay resource (from the UIRC boot
-// chunk) at runtime.
-class ResourceUse : public ActionRecord {
+// chunk) at runtime -- e.g. paying coins from the purse (resource 0). Applying
+// the change plays a sound, optionally shows a transient overlay (a sprite and/or
+// the resource's numeric value) and can change scene on success.
+class ResourceUse : public RenderActionRecord {
 public:
+	ResourceUse() : RenderActionRecord(7) {}
+
+	void init() override;
 	void readData(Common::SeekableReadStream &stream) override;
 	void execute() override;
+	void handleInput(NancyInput &input) override;
+
+	bool isViewportRelative() const override { return true; }
 
 protected:
 	Common::String getRecordTypeName() const override { return "ResourceUse"; }
 
+	// Applies the resource change (respecting affordability), sets the event
+	// flag and starts the matching outcome sound.
+	void applyChange();
+
 	int16 _resourceIndex = 0;
 	int16 _amount = 0;
 	byte _mode = 0;        // 0 = set the resource, non-zero = add (clamped to >= 0)
 	FlagDescription _flag; // event flag set when the change is applied
+
+	Common::String _failSoundName;    // played when the change can't be applied
+	Common::String _successSoundName; // played when it is applied
+
+	// When this rect is non-degenerate the change is interactive: the player
+	// clicks it (e.g. a coin slot) to pay. A degenerate rect applies at once.
+	Common::Rect _paymentHotspot;
+	byte _useResourceCursor = 0;      // 0 = normal cursor, else the resource's own hover cursor
+
+	uint16 _sceneID = kNoScene;       // scene entered on success (9999 = none)
+	uint16 _continueSceneSound = 0;
+
+	bool _drawResourceOverlay = false; // blit the resource's UIRC sprite
+	Common::Point _overlayDest;
+	bool _drawResourceValue = false;   // draw the resource's numeric value
+	Common::Point _valueDest;
+
+	SoundDescription _sound;
+	bool _hasSound = false;
+	bool _interactive = false;
+	bool _paymentResolved = false;
+	bool _paymentApplied = false;
 };
 
 } // End of namespace Action




More information about the Scummvm-git-logs mailing list