[Scummvm-git-logs] scummvm master -> d628bd41df34d169c76ef82968e40d5e4cc498d7

bluegr noreply at scummvm.org
Sat Jul 18 13:02:25 UTC 2026


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

Summary:
18190b1c02 NANCY: NANCY8: Slight cleanup in AngleTossPuzzle
cfcfaf640a NANCY: NANCY13: Implement new PlayRandomMovie functionality
d628bd41df NANCY: Better file naming for exported AR dumps


Commit: 18190b1c02811e85e035306d59671905dbec187a
    https://github.com/scummvm/scummvm/commit/18190b1c02811e85e035306d59671905dbec187a
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-18T16:02:14+03:00

Commit Message:
NANCY: NANCY8: Slight cleanup in AngleTossPuzzle

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


diff --git a/engines/nancy/action/puzzle/angletosspuzzle.cpp b/engines/nancy/action/puzzle/angletosspuzzle.cpp
index 6f08628850f..4877d2d0a9a 100644
--- a/engines/nancy/action/puzzle/angletosspuzzle.cpp
+++ b/engines/nancy/action/puzzle/angletosspuzzle.cpp
@@ -83,7 +83,7 @@ void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
 
 	_throwSquidScene.readData(stream);	// data+0x220, ends at data+0x238
 
-	// HACK: We've skipped some of the flag data at this point,
+	// WORKAROUND: We've skipped some of the flag data at this point,
 	// so go back to read them correctly
 	_throwSquidScene._flag.label = kEvNoEvent;
 	_throwSquidScene._flag.flag = 0;
@@ -132,19 +132,14 @@ void AngleTossPuzzle::execute() {
 			// 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!
 				NancySceneState.setEventFlag(_winFlag, g_nancy->_true);
 			} else if (_curPower > _targetPower) {
-				// Power too strong
 				NancySceneState.setEventFlag(_powerTooStrongFlag, g_nancy->_true);
 			} else if (_curPower < _targetPower) {
-				// Power too weak
 				NancySceneState.setEventFlag(_powerTooWeakFlag, g_nancy->_true);
 			} else if (_curAngle > _targetAngle) {
-				// Angle too far right
 				NancySceneState.setEventFlag(_angleTooRightFlag, g_nancy->_true);
 			} else if (_curAngle < _targetAngle) {
-				// Angle too far left
 				NancySceneState.setEventFlag(_angleTooLeftFlag, g_nancy->_true);
 			}
 
@@ -245,7 +240,6 @@ void AngleTossPuzzle::handleInput(NancyInput &input) {
 			_drawSurface.blitFrom(_image, _throwSprite, _throwDisplay);
 			_needsRedraw = true;
 		}
-		return;
 	}
 }
 


Commit: cfcfaf640a4916f36b74551d94515aedf4ae559c
    https://github.com/scummvm/scummvm/commit/cfcfaf640a4916f36b74551d94515aedf4ae559c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-18T16:02:14+03:00

Commit Message:
NANCY: NANCY13: Implement new PlayRandomMovie functionality

Fixes character idle/fidgeting animations

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


diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 70854e4e200..99279aa5947 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -55,13 +55,30 @@ PlaySecondaryMovie::~PlaySecondaryMovie() {
 	}
 }
 
+bool PlaySecondaryMovie::isPersistentAcrossScenes() const {
+	// Nancy11's random movies can be ambient loops that intentionally keep
+	// playing across scene changes. Nancy13's per-character reaction movies
+	// (AR 42) are scene-local: they must stop when their scene is left, and are
+	// reloaded if it's re-entered.
+	return _isRandom && g_nancy->getGameType() < kGameTypeNancy13 && !_isDone && !_randomStopRequested;
+}
+
 void PlaySecondaryMovie::readRandomSequence(Common::Serializer &ser, RandomSequence &seq) {
 	readFilename(ser, seq.name);
 	ser.syncAsUint16LE(seq.startFrame);
 	ser.syncAsUint16LE(seq.lastFrame);
 	ser.syncAsSint32LE(seq.minPauseMs);
 	ser.syncAsSint32LE(seq.maxPauseMs);
-	ser.syncAsUint16LE(seq.stayWeight);
+
+	if (g_nancy->getGameType() >= kGameTypeNancy13) {
+		// Percent (0-100) chance to stay on this sequence and pause, rather
+		// than transition to one of the next sequences.
+		byte pauseChance = 0;
+		ser.syncAsByte(pauseChance);
+		seq.stayWeight = pauseChance;
+	} else {
+		ser.syncAsUint16LE(seq.stayWeight);
+	}
 
 	uint16 nextCount = 0;
 	ser.syncAsUint16LE(nextCount);
@@ -73,19 +90,54 @@ void PlaySecondaryMovie::readRandomSequence(Common::Serializer &ser, RandomSeque
 	}
 }
 
+void PlaySecondaryMovie::readSecondaryRandomMovie(Common::Serializer &ser, RandomSequence &seq) {
+	// Nancy13's random-movie chunk carries one extra "secondary" movie record
+	// (e.g. a character's recognition animation) between the sequence list and
+	// the hotspot list. It is a name + 5 uint16 + a next-list; only the first
+	// two uint16s (start/last frame) have known roles so far, but the whole
+	// record must be consumed here or the hotspot list below misaligns.
+	readFilename(ser, seq.name);
+	ser.syncAsUint16LE(seq.startFrame);
+	ser.syncAsUint16LE(seq.lastFrame);
+	ser.skip(6);	// three uint16 fields, roles not yet characterized
+
+	uint16 nextCount = 0;
+	ser.syncAsUint16LE(nextCount);
+	seq.nextSequences.resize(nextCount);
+	for (uint i = 0; i < nextCount; ++i) {
+		readFilename(ser, seq.nextSequences[i].name);
+		ser.syncAsUint16LE(seq.nextSequences[i].weight);
+	}
+}
+
 void PlaySecondaryMovie::readRandomMovieData(Common::Serializer &ser, Common::SeekableReadStream &stream) {
 	readFilename(ser, _startingSequenceName);
 	ser.syncAsUint16LE(_randomPlayerCursorAllowed);
 
 	uint16 sequenceCount = 0, hotspotCount = 0;
-	ser.syncAsUint16LE(sequenceCount);
-	ser.syncAsUint16LE(hotspotCount);
+
+	if (g_nancy->getGameType() >= kGameTypeNancy13) {
+		// Nancy13 replaced the inline hotspot count with two header fields (a
+		// flag tested against 1, and a u16); the hotspot list is now length-
+		// prefixed after the sequences instead.
+		ser.skip(2);
+		ser.skip(2);
+		ser.syncAsUint16LE(sequenceCount);
+	} else {
+		ser.syncAsUint16LE(sequenceCount);
+		ser.syncAsUint16LE(hotspotCount);
+	}
 
 	_sequences.resize(sequenceCount);
 	for (uint i = 0; i < sequenceCount; ++i) {
 		readRandomSequence(ser, _sequences[i]);
 	}
 
+	if (g_nancy->getGameType() >= kGameTypeNancy13) {
+		readSecondaryRandomMovie(ser, _secondaryMovie);
+		ser.syncAsUint16LE(hotspotCount);
+	}
+
 	_videoDescs.resize(hotspotCount);
 	for (uint i = 0; i < hotspotCount; ++i) {
 		_videoDescs[i].readData(stream);
@@ -142,11 +194,26 @@ bool PlaySecondaryMovie::activateRandomSequence(int index) {
 		return false;
 	}
 
+	resolveSentinelFrames();
+
 	_isFinished = false;
 	_curViewportFrame = -1;	// force visibility re-evaluation next tick
 	return true;
 }
 
+void PlaySecondaryMovie::resolveSentinelFrames() {
+	// Random sequences use -1/-2 for the start/last frame to mean "play the
+	// movie's own first/last frame". Resolve them now that the decoder (and
+	// thus the real frame count) is available.
+	if (_firstFrame == 0xFFFF) {
+		_firstFrame = 0;
+	}
+	if (_lastFrame == 0xFFFE || _lastFrame == 0xFFFF) {
+		int frameCount = _decoder.getFrameCount();
+		_lastFrame = frameCount > 0 ? (uint16)(frameCount - 1) : 0;
+	}
+}
+
 void PlaySecondaryMovie::playRandomSequence() {
 	if (!_isRandom || _sequences.empty()) {
 		return;
@@ -157,6 +224,28 @@ void PlaySecondaryMovie::playRandomSequence() {
 	activateRandomSequence(picked);
 }
 
+int PlaySecondaryMovie::beginRandomPause(const RandomSequence &seq) {
+	int32 pauseMs = seq.minPauseMs;
+	if (seq.maxPauseMs > seq.minPauseMs) {
+		pauseMs += g_nancy->_randomSource->getRandomNumber(seq.maxPauseMs - seq.minPauseMs - 1);
+	}
+	_randomPauseEndTime = g_system->getMillis() + (uint32)MAX<int32>(0, pauseMs);
+	_randomChainState = kRandomPaused;
+	setVisible(false);
+	_decoder.pauseVideo(true);
+	return -1;
+}
+
+int PlaySecondaryMovie::lookupSequence(const Common::Path &name) const {
+	for (uint j = 0; j < _sequences.size(); ++j) {
+		if (_sequences[j].name == name) {
+			return (int)j;
+		}
+	}
+	warning("PlayRandomMovie: next-sequence \"%s\" not part of this AR", name.toString().c_str());
+	return -1;
+}
+
 int PlaySecondaryMovie::rollNextSequence() {
 	if (_activeSequenceIndex < 0 || _activeSequenceIndex >= (int)_sequences.size()) {
 		return -1;
@@ -164,6 +253,38 @@ int PlaySecondaryMovie::rollNextSequence() {
 
 	const RandomSequence &seq = _sequences[_activeSequenceIndex];
 
+	if (g_nancy->getGameType() >= kGameTypeNancy13) {
+		// Two independent rolls: first a percent chance to stay on this
+		// sequence and pause, then a percent-weighted pick among the next
+		// sequences (weights sum to 100, or all EQUAL_CHANCE for a uniform pick).
+		if (seq.stayWeight != 0 && (uint)g_nancy->_randomSource->getRandomNumber(99) < seq.stayWeight) {
+			return beginRandomPause(seq);
+		}
+
+		if (seq.nextSequences.empty()) {
+			_randomChainState = kRandomPaused;
+			_randomPauseEndTime = g_system->getMillis() + 1000;	// re-check in 1s
+			return -1;
+		}
+
+		const bool equalChance = seq.nextSequences[0].weight == 0xFFFF;
+		const uint step = 100 / seq.nextSequences.size();
+		uint roll = g_nancy->_randomSource->getRandomNumber(99);
+		uint cumulative = 0;
+		for (uint i = 0; i < seq.nextSequences.size(); ++i) {
+			if (i == seq.nextSequences.size() - 1) {
+				cumulative = 100;
+			} else {
+				cumulative += equalChance ? step : seq.nextSequences[i].weight;
+			}
+			if (roll < cumulative) {
+				return lookupSequence(seq.nextSequences[i].name);
+			}
+		}
+
+		return -1;
+	}
+
 	uint32 totalWeight = seq.stayWeight;
 	for (const NextSequenceRef &ns : seq.nextSequences) {
 		totalWeight += ns.weight;
@@ -179,30 +300,14 @@ int PlaySecondaryMovie::rollNextSequence() {
 	uint32 roll = g_nancy->_randomSource->getRandomNumber(totalWeight - 1);
 
 	if (roll < seq.stayWeight) {
-		int32 pauseMs = seq.minPauseMs;
-		if (seq.maxPauseMs > seq.minPauseMs) {
-			pauseMs += g_nancy->_randomSource->getRandomNumber(seq.maxPauseMs - seq.minPauseMs - 1);
-		}
-		_randomPauseEndTime = g_system->getMillis() + (uint32)MAX<int32>(0, pauseMs);
-		_randomChainState = kRandomPaused;
-		setVisible(false);
-		_decoder.pauseVideo(true);
-		return -1;
+		return beginRandomPause(seq);
 	}
 
 	uint32 cumulative = seq.stayWeight;
 	for (uint i = 0; i < seq.nextSequences.size(); ++i) {
 		cumulative += seq.nextSequences[i].weight;
 		if (roll < cumulative) {
-			// Look up the named sequence in _sequences[].
-			for (uint j = 0; j < _sequences.size(); ++j) {
-				if (_sequences[j].name == seq.nextSequences[i].name) {
-					return (int)j;
-				}
-			}
-			warning("PlayRandomMovie: next-sequence \"%s\" not part of this AR",
-				seq.nextSequences[i].name.toString().c_str());
-			return -1;
+			return lookupSequence(seq.nextSequences[i].name);
 		}
 	}
 
@@ -354,6 +459,10 @@ void PlaySecondaryMovie::init() {
 		}
 	}
 
+	if (_isRandom) {
+		resolveSentinelFrames();
+	}
+
 	_screenPosition = _drawSurface.getBounds();
 
 	RenderObject::init();
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 7d754ebc69e..4a1d659f0ca 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -121,6 +121,11 @@ public:
 	uint16 _randomPlayerCursorAllowed = kPlayerCursorAllowed;
 	Common::Array<RandomSequence> _sequences;
 
+	// Nancy13+ carries one extra "secondary" movie (e.g. a recognition
+	// animation) after the sequence list. Stored for future playback; reading
+	// it is required so the trailing hotspot list stays aligned.
+	RandomSequence _secondaryMovie;
+
 	// Chain state. After a sequence's movie finishes the engine rolls a
 	// weighted pick: "stay" -> enter pause for a random duration and
 	// re-roll; valid next-sequence -> swap to that sequence's movie.
@@ -138,9 +143,7 @@ public:
 
 	bool isViewportRelative() const override { return true; }
 
-	bool isPersistentAcrossScenes() const override {
-		return _isRandom && !_isDone && !_randomStopRequested;
-	}
+	bool isPersistentAcrossScenes() const override;
 
 	Common::String getRecordExtraInfo() const override { return Common::String::format("Scene %d", _sceneChange.sceneID); }
 
@@ -153,6 +156,7 @@ protected:
 	// needed for SecondaryVideoDescription::readData.
 	void readRandomMovieData(Common::Serializer &ser, Common::SeekableReadStream &stream);
 	void readRandomSequence(Common::Serializer &ser, RandomSequence &seq);
+	void readSecondaryRandomMovie(Common::Serializer &ser, RandomSequence &seq);
 
 	void readDataNancy14(Common::Serializer &ser, Common::SeekableReadStream &stream);
 
@@ -165,6 +169,17 @@ protected:
 	// or the chosen sequence index otherwise.
 	int rollNextSequence();
 
+	// Enter the paused chain state for a random duration in the sequence's
+	// [minPauseMs, maxPauseMs] range. Always returns -1.
+	int beginRandomPause(const RandomSequence &seq);
+
+	// Find a sequence by name, warning and returning -1 if it isn't present.
+	int lookupSequence(const Common::Path &name) const;
+
+	// Resolve the -1/-2 "whole movie" sentinels in _firstFrame/_lastFrame
+	// against the loaded decoder's frame count. Random sequences only.
+	void resolveSentinelFrames();
+
 	Graphics::ManagedSurface _fullFrame;
 	int _curViewportFrame = -1;
 	bool _isFinished = false;


Commit: d628bd41df34d169c76ef82968e40d5e4cc498d7
    https://github.com/scummvm/scummvm/commit/d628bd41df34d169c76ef82968e40d5e4cc498d7
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-18T16:02:15+03:00

Commit Message:
NANCY: Better file naming for exported AR dumps

Changed paths:
    engines/nancy/console.cpp


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




More information about the Scummvm-git-logs mailing list