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

sev- noreply at scummvm.org
Thu Apr 9 10:55:54 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:
1c250c7879 DIRECTOR: Initial code for replacing FilmLoop parsing with Score
dc59f8d49d DIRECTOR: Force sprite preload for FilmLoop castmembers
59eba8a56e DIRECTOR: Remove custom loaders for Filmloop castmembers
bdfe5780a5 DIRECTOR: Completely disable Lingo in filmloops
e720eaf755 DIRECTOR: Let Filmloop castmember Score keep its own state
828966aeb7 DIRECTOR: Streamlined filmloop sprites passing
13836af96c DIRECTOR: Filmloop: Fix filmloop scaling bug
b0f7359876 DIRECTOR: Fix crash when setting picture of unloaded bitmap cast member


Commit: 1c250c7879e8e00748758dedab56a56ecc7432b3
    https://github.com/scummvm/scummvm/commit/1c250c7879e8e00748758dedab56a56ecc7432b3
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:27+02:00

Commit Message:
DIRECTOR: Initial code for replacing FilmLoop parsing with Score

Changed paths:
    engines/director/castmember/filmloop.cpp
    engines/director/castmember/filmloop.h
    engines/director/score.cpp


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index d0a540b8cea..fd170f64d23 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -31,6 +31,7 @@
 #include "director/channel.h"
 #include "director/frame.h"
 #include "director/movie.h"
+#include "director/score.h"
 #include "director/sprite.h"
 #include "director/window.h"
 #include "director/castmember/bitmap.h"
@@ -85,7 +86,7 @@ FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, FilmLoopCastMe
 	_enableSound = source._enableSound;
 	_crop = source._crop;
 	_center = source._center;
-	_frames = source._frames;
+	_score = source._score;
 	_subchannels = source._subchannels;
 	_looping = source._looping;
 }
@@ -95,7 +96,7 @@ FilmLoopCastMember::~FilmLoopCastMember() {
 }
 
 bool FilmLoopCastMember::isModified() {
-	if (_frames.size())
+	if (_score->_scoreCache.size())
 		return true;
 
 	if (_initialRect.width() && _initialRect.height())
@@ -109,19 +110,17 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 
 	_subchannels.clear();
 
-	if (frame >= _frames.size()) {
-		warning("FilmLoopCastMember::getSubChannels(): Film loop frame %d requested, only %d available", frame, _frames.size());
+	if (frame >= _score->_scoreCache.size()) {
+		warning("FilmLoopCastMember::getSubChannels(): Film loop frame %d requested, only %d available", frame, _score->_scoreCache.size());
 		return &_subchannels;
 	}
 
 	// get the list of sprite IDs for this frame
 	Common::Array<int> spriteIds;
-	for (auto &iter : _frames[frame].sprites) {
-		// channels 0 and 1 are used for audio
-		if (iter._key >= 2)
-			spriteIds.push_back(iter._key);
+	for (uint i = 0; i < _score->_scoreCache[frame]->_sprites.size(); ++i) {
+		if (_score->_scoreCache[frame]->_sprites[i] && !_score->_scoreCache[frame]->_sprites[i]->_castId.isNull())
+			spriteIds.push_back(i);
 	}
-	Common::sort(spriteIds.begin(), spriteIds.end());
 
 	debugC(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): castId: %d, frame: %d, count: %d, initRect: %d,%d %dx%d, bbox: %d,%d %dx%d",
 			_castId, frame, spriteIds.size(),
@@ -134,33 +133,33 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 
 	// copy the sprites in order to the list
 	for (auto &iter : spriteIds) {
-		Sprite src = _frames[frame].sprites[iter];
-		if (!src._cast)
+		Sprite *src = _score->_scoreCache[frame]->_sprites[iter];
+		if (src->_castId.isNull())
 			continue;
 		// translate sprite relative to the global bounding box
-		int16 relX = (src._startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
-		int16 relY = (src._startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
+		int16 relX = (src->_startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
+		int16 relY = (src->_startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
 		int16 absX = relX + bbox.left;
 		int16 absY = relY + bbox.top;
-		int16 width = src._width * widgetRect.width() / _initialRect.width();
-		int16 height = src._height * widgetRect.height() / _initialRect.height();
+		int16 width = src->_width * widgetRect.width() / _initialRect.width();
+		int16 height = src->_height * widgetRect.height() / _initialRect.height();
 
 		debugC(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): sprite: %d - cast: %s, orig: %d,%d %dx%d, trans: %d,%d %dx%d",
-				iter, src._castId.asString().c_str(),
-				src._startPoint.x, src._startPoint.y, src._width, src._height,
+				iter, src->_castId.asString().c_str(),
+				src->_startPoint.x, src->_startPoint.y, src->_width, src->_height,
 				absX, absY, width, height);
 
 		// Re-inject the translated position into the Sprite.
 		// This saves the hassle of having to force the Channel to be in puppet mode.
-		src._width = width;
-		src._height = height;
-		src._startPoint = Common::Point(absX, absY);
-		src._stretch = true;
+		src->_width = width;
+		src->_height = height;
+		src->_startPoint = Common::Point(absX, absY);
+		src->_stretch = true;
 
 		// Film loop frames are constructed as a series of Channels, much like how a normal frame
 		// is rendered by the Score. We don't include a pointer to the current Score here,
 		// that's only for querying the constraint channel which is not used.
-		Channel chan(nullptr, &src);
+		Channel chan(nullptr, src);
 		_subchannels.push_back(chan);
 	}
 	// Initialise the widgets on all of the subchannels.
@@ -175,29 +174,24 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 }
 
 CastMemberID FilmLoopCastMember::getSubChannelSound1(uint frame) {
-	if (frame >= _frames.size()) {
-		warning("FilmLoopCastMember::getSubChannelSound1(): Film loop frame %d requested, only %d available", frame, _frames.size());
+	if (frame >= _score->_scoreCache.size()) {
+		warning("FilmLoopCastMember::getSubChannelSound1(): Film loop frame %d requested, only %d available", frame, _score->_scoreCache.size());
 		return CastMemberID();
 	}
 
-	if (_frames[frame].sprites.contains(0)) {
-		return _frames[frame].sprites[0]._castId;
-	}
-	return CastMemberID();
+	return _score->_scoreCache[frame]->_mainChannels.sound1;
 }
 
 CastMemberID FilmLoopCastMember::getSubChannelSound2(uint frame) {
-	if (frame >= _frames.size()) {
-		warning("FilmLoopCastMember::getSubChannelSound2(): Film loop frame %d requested, only %d available", frame, _frames.size());
+	if (frame >= _score->_scoreCache.size()) {
+		warning("FilmLoopCastMember::getSubChannelSound2(): Film loop frame %d requested, only %d available", frame, _score->_scoreCache.size());
 		return CastMemberID();
 	}
 
-	if (_frames[frame].sprites.contains(1)) {
-		return _frames[frame].sprites[1]._castId;
-	}
-	return CastMemberID();
+	return _score->_scoreCache[frame]->_mainChannels.sound2;
 }
 
+#if 0
 void FilmLoopCastMember::loadFilmLoopDataD2(Common::SeekableReadStreamEndian &stream) {
 	_initialRect = Common::Rect();
 	_frames.clear();
@@ -667,7 +661,7 @@ void FilmLoopCastMember::loadFilmLoopDataD6(Common::SeekableReadStreamEndian &st
 	debugC(5, kDebugLoading, "loadFilmLoopDataD6: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
 }
 
-
+#endif
 
 Common::String FilmLoopCastMember::formatInfo() {
 	return Common::String::format(
@@ -676,7 +670,7 @@ Common::String FilmLoopCastMember::formatInfo() {
 		_initialRect.left, _initialRect.top,
 		_boundingRect.width(), _boundingRect.height(),
 		_boundingRect.left, _boundingRect.top,
-		_frames.size(), _subchannels.size(), _enableSound, _looping,
+		_score->_scoreCache.size(), _subchannels.size(), _enableSound, _looping,
 		_crop, _center
 	);
 }
@@ -685,48 +679,39 @@ void FilmLoopCastMember::load() {
 	if (_loaded)
 		return;
 
+	Common::SeekableReadStreamEndian *loop = nullptr;
+	uint16 filmLoopId = 0;
+		uint32 tag = 0;
+
 	if (_cast->_version < kFileVer400) {
 		// Director 3 and below should have a SCVW resource
-		uint16 filmLoopId = _castId + _cast->_castIDoffset;
-		uint32 tag = MKTAG('S', 'C', 'V', 'W');
-		Common::SeekableReadStreamEndian *loop = _cast->getResource(tag, filmLoopId);
-		if (loop) {
-			debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-			loadFilmLoopDataD2(*loop);
-			delete loop;
-		} else {
-			warning("FilmLoopCastMember::load(): Film loop not found");
-		}
+		filmLoopId = _castId + _cast->_castIDoffset;
+		tag = MKTAG('S', 'C', 'V', 'W');
+		loop = _cast->getResource(tag, filmLoopId);
 	} else if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer700) {
-		Common::SeekableReadStreamEndian *loop = nullptr;
-		uint16 filmLoopId = 0;
-		uint32 tag = 0;
 		for (auto &it : _children) {
 			if (it.tag == MKTAG('S', 'C', 'V', 'W')) {
 				filmLoopId = it.index;
 				tag = it.tag;
 				loop = _cast->getResource(tag, filmLoopId);
 				break;
+			} else {
+				debugC(5, kDebugLoading, "FilmLoopCastMember::load(): Ignoring child with tag '%s' id: %d", tag2str(it.tag), it.index);
 			}
 		}
-
-		if (loop) {
-			debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-			if (_cast->_version < kFileVer500) {
-				loadFilmLoopDataD4(*loop);
-			} else if (_cast->_version < kFileVer600) {
-				loadFilmLoopDataD5(*loop);
-			} else if (_cast->_version < kFileVer700) {
-				loadFilmLoopDataD6(*loop);
-			}
-			delete loop;
-		} else {
-			warning("FilmLoopCastMember::load(): No SCVW resource found in %d children", _children.size());
-		}
 	} else {
 		warning("STUB: FilmLoopCastMember::load(): Film loops not yet supported for version v%d (%d)", humanVersion(_cast->_version), _cast->_version);
 	}
 
+	if (loop) {
+		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
+		_score = new Score(g_director->getCurrentMovie());
+		_score->loadFrames(*loop, _cast->_version);
+		delete loop;
+	} else {
+		warning("FilmLoopCastMember::load(): Film loop not found");
+	}
+
 	_loaded = true;
 }
 
@@ -821,11 +806,10 @@ void FilmLoopCastMember::writeSCVWResource(Common::SeekableWriteStream *writeStr
 			// order of message (this order tells us the channel we're reading) ->
 			// 1-20 bytes of Sprite data
 
-	for (FilmLoopFrame frame : _frames) {
-		writeStream->writeUint16BE(frame.sprites.size() * (channelSize + 4) + 2);					// Frame Size
+	for (Frame *frame : _score->_scoreCache) {
+		writeStream->writeUint16BE(frame->_sprites.size() * (channelSize + 4) + 2);					// Frame Size
 
-		for (auto it : frame.sprites) {
-			int channel = it._key;
+		for (uint channel = 0; channel < frame->_sprites.size(); ++channel) {
 			// TODO: For now writing the order considering that each sprite will have 20 bytes of data
 			// In the future, for optimization, we can actually calculate the data of each sprite
 			// And write the order accordingly
@@ -836,12 +820,12 @@ void FilmLoopCastMember::writeSCVWResource(Common::SeekableWriteStream *writeStr
 			writeStream->writeUint16BE(channelSize);						// message width
 			writeStream->writeUint16BE(channel * channelSize);
 
-			Sprite sprite = it._value;
+			Sprite *sprite = frame->_sprites[channel];
 
 			if (_cast->_version >= kFileVer400 && _cast->_version < kFileVer500) {
-				writeSpriteDataD4(writeStream, sprite);
+				writeSpriteDataD4(writeStream, *sprite);
 			} else if (_cast->_version >= kFileVer500 && _cast->_version < kFileVer600) {
-				writeSpriteDataD5(writeStream, sprite);
+				writeSpriteDataD5(writeStream, *sprite);
 			}
 		}
 
@@ -875,15 +859,14 @@ uint32 FilmLoopCastMember::getSCVWResourceSize() {
 	}
 
 	uint32 framesSize = 0;
-	for (FilmLoopFrame frame : _frames) {
+	for (Frame *frame : _score->_scoreCache) {
 		// Frame size
 		framesSize += 2;
-		for (auto it : frame.sprites) {
-			// message width: 2 bytes
-			// order: 2 bytes
-			// Sprite data: 20 bytes
-			framesSize += 2 + 2 + channelSize;
-		}
+
+		// message width: 2 bytes
+		// order: 2 bytes
+		// Sprite data: 20 bytes
+		framesSize += (2 + 2 + channelSize) * frame->_sprites.size();
 	}
 
 	// Size: 4 bytes
diff --git a/engines/director/castmember/filmloop.h b/engines/director/castmember/filmloop.h
index 5cd04417f32..80006e5ef65 100644
--- a/engines/director/castmember/filmloop.h
+++ b/engines/director/castmember/filmloop.h
@@ -72,8 +72,9 @@ public:
 	bool _crop;
 	bool _center;
 
-	Common::Array<FilmLoopFrame> _frames;
 	Common::Array<Channel> _subchannels;
+
+	Score *_score;
 };
 
 } // End of namespace Director
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 386eddc8bff..a50cd3d634f 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -908,12 +908,12 @@ void Score::incrementFilmLoops() {
 	for (auto &it : _channels) {
 		if (it->_sprite->_cast && (it->_sprite->_cast->_type == kCastFilmLoop || it->_sprite->_cast->_type == kCastMovie)) {
 			FilmLoopCastMember *fl = ((FilmLoopCastMember *)it->_sprite->_cast);
-			if (!fl->_frames.empty()) {
+			if (!fl->_score->_scoreCache.empty()) {
 				// increment the film loop counter
 				if (fl->_looping) {
 					it->_filmLoopFrame += 1;
-					it->_filmLoopFrame %= fl->_frames.size();
-				} else if (it->_filmLoopFrame < (fl->_frames.size() - 1)) {
+					it->_filmLoopFrame %= fl->_score->_scoreCache.size();
+				} else if (it->_filmLoopFrame < (fl->_score->_scoreCache.size() - 1)) {
 					it->_filmLoopFrame += 1;
 				}
 			} else {


Commit: dc59f8d49dc6acc2c0db94c3e955c6d46c453f78
    https://github.com/scummvm/scummvm/commit/dc59f8d49dc6acc2c0db94c3e955c6d46c453f78
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Force sprite preload for FilmLoop castmembers

Changed paths:
    engines/director/castmember/filmloop.cpp
    engines/director/score.cpp
    engines/director/score.h


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index fd170f64d23..ca365c3bf61 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -704,9 +704,9 @@ void FilmLoopCastMember::load() {
 	}
 
 	if (loop) {
-		debugC(2, kDebugLoading, "****** Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
+		debugC(2, kDebugLoading, "****** FilmLoopCastMember::load(): Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
 		_score = new Score(g_director->getCurrentMovie());
-		_score->loadFrames(*loop, _cast->_version);
+		_score->loadFrames(*loop, _cast->_version, true);
 		delete loop;
 	} else {
 		warning("FilmLoopCastMember::load(): Film loop not found");
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index a50cd3d634f..e2a31dd17ae 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -1832,7 +1832,7 @@ void Score::playQueuedSound() {
 	sound->playFPlaySound();
 }
 
-void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version) {
+void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version, bool loadSprites) {
 	debugC(1, kDebugLoading, "****** Loading frames VWSC");
 
 	// Setup our streams for frames processing
@@ -1955,7 +1955,7 @@ void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version)
 
 	// Calculate number of frames and their positions
 	// numOfFrames in the header is often incorrect
-	for (_numFrames = 1; loadFrame(_numFrames, false); _numFrames++) {
+	for (_numFrames = 1; loadFrame(_numFrames, loadSprites); _numFrames++) {
 		_scoreCache.push_back(new Frame(*_currentFrame));
 
 		for (int i = 0; i < (int)_currentFrame->_sprites.size(); i++) {
diff --git a/engines/director/score.h b/engines/director/score.h
index bec69ff42a8..33329321bd9 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -70,7 +70,7 @@ public:
 
 	Movie *getMovie() const { return _movie; }
 
-	void loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version);
+	void loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version, bool loadSprites = false);
 	bool loadFrame(int frame, bool loadCast);
 	bool readOneFrame();
 	void updateFrame(Frame *frame);


Commit: 59eba8a56e5b65f48f902a02bc63e556d67a2ef2
    https://github.com/scummvm/scummvm/commit/59eba8a56e5b65f48f902a02bc63e556d67a2ef2
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Remove custom loaders for Filmloop castmembers

Changed paths:
    engines/director/castmember/filmloop.cpp


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index ca365c3bf61..f069ea0ce0e 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -191,478 +191,6 @@ CastMemberID FilmLoopCastMember::getSubChannelSound2(uint frame) {
 	return _score->_scoreCache[frame]->_mainChannels.sound2;
 }
 
-#if 0
-void FilmLoopCastMember::loadFilmLoopDataD2(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "loadFilmLoopDataD2: SCVW body:");
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint16 channelSize = kSprChannelSizeD2;
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE();
-		if (frameSize == 0) {
-			continue;
-		}
-		frameSize -= 2;
-		if (debugChannelSet(5, kDebugLoading)) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD2: Frame entry:");
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			int msgWidth = stream.readByte() * 2;
-			int order = stream.readByte() * 2;
-			frameSize -= 2;
-
-			int channel = order / channelSize;
-			int channelOffset = order % channelSize;
-			int offset = order;
-
-			debugC(8, kDebugLoading, "loadFilmLoopDataD2: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-
-			uint16 segSize = msgWidth;
-			uint16 nextStart = (channel + 1) * kSprChannelSizeD2;
-
-			while (segSize > 0) {
-				Sprite sprite(nullptr);
-				sprite._movie = g_director->getCurrentMovie();
-				if (newFrame.sprites.contains(channel)) {
-					sprite = newFrame.sprites.getVal(channel);
-				}
-
-				sprite._spriteType = kCastMemberSprite;
-				sprite._stretch = true;
-
-				uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
-				int startPosition = stream.pos() - channelOffset;
-				int finishPosition = stream.pos() + needSize;
-				readSpriteDataD2(stream, sprite, startPosition, finishPosition);
-				newFrame.sprites.setVal(channel, sprite);
-				segSize -= needSize;
-				offset += needSize;
-				channel += 1;
-				channelOffset = 0;
-				nextStart += kSprChannelSizeD2;
-			}
-
-			frameSize -= msgWidth;
-		}
-
-		for (auto &s : newFrame.sprites) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD2: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-					s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-					s._value._width, s._value._height);
-
-			s._value.setCast(s._value._castId);
-			Common::Point topLeft = s._value._startPoint;
-			if (s._value._cast) {
-				topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
-			}
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s._value._width,
-				topLeft.y + s._value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "loadFilmLoopDataD2: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "loadFilmLoopDataD2: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-}
-
-void FilmLoopCastMember::loadFilmLoopDataD4(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(8, kDebugLoading)) {
-		debugC(8, kDebugLoading, "loadFilmLoopDataD4: SCVW body of size: %d", size);
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint32 framesOffset = stream.readUint32BE();
-	if (debugChannelSet(8, kDebugLoading)) {
-		debugC(8, kDebugLoading, "loadFilmLoopDataD4: SCVW header of size: %d", framesOffset - 8);
-		stream.hexdump(framesOffset - 8);
-	}
-	stream.skip(6);
-	uint16 channelSize = kSprChannelSizeD4;
-	stream.readUint16BE(); // should be kSprChannelSizeD4 = 20!
-	stream.skip(framesOffset - 16);
-
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE();
-		if (frameSize == 0) {
-			continue;
-		}
-		frameSize -= 2;
-		if (debugChannelSet(8, kDebugLoading)) {
-			debugC(8, kDebugLoading, "loadFilmLoopDataD4: Frame entry: %d", frameSize);
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			uint16 msgWidth = stream.readUint16BE();
-			uint16 order = stream.readUint16BE();
-			frameSize -= 4;
-
-			int channel = order / channelSize;
-			int channelOffset = order % channelSize;
-			int offset = order;
-
-			debugC(8, kDebugLoading, "loadFilmLoopDataD4: Message: msgWidth %d, order: %d, channel %d, channelOffset %d", msgWidth, order, channel, channelOffset);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-
-			uint16 segSize = msgWidth;
-			uint16 nextStart = (channel + 1) * kSprChannelSizeD4;
-
-			while (segSize > 0) {
-				Sprite sprite(nullptr);
-				sprite._movie = g_director->getCurrentMovie();
-				if (newFrame.sprites.contains(channel)) {
-					sprite = newFrame.sprites.getVal(channel);
-				}
-
-				sprite._stretch = true;
-
-				uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
-				int startPosition = stream.pos() - channelOffset;
-				int finishPosition = stream.pos() + needSize;
-				readSpriteDataD4(stream, sprite, startPosition, finishPosition);
-				newFrame.sprites.setVal(channel, sprite);
-				segSize -= needSize;
-				offset += needSize;
-				channel += 1;
-				channelOffset = 0;
-				nextStart += kSprChannelSizeD4;
-			}
-
-			frameSize -= msgWidth;
-		}
-
-		for (auto &s : newFrame.sprites) {
-			debugC(8, kDebugLoading, "loadFilmLoopDataD4: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-					s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-					s._value._width, s._value._height);
-
-			if (s._key == -1) {
-				debugC(8, kDebugLoading, "loadFilmLoopDataD4: Skipping channel -1");
-				if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
-						 (s._value._height != -256 && s._value._height != 0))
-					warning("BUILDBOT: loadFilmLoopDataD4: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-						s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-						s._value._width, s._value._height);
-				continue;
-			}
-
-			s._value.setCast(s._value._castId);
-			Common::Point topLeft = s._value._startPoint;
-			if (s._value._cast) {
-				topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
-			}
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s._value._width,
-				topLeft.y + s._value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "loadFilmLoopDataD4: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "loadFilmLoopDataD4: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-}
-
-void FilmLoopCastMember::loadFilmLoopDataD5(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "loadFilmLoopDataD5: SCVW body:");
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint32 framesOffset = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "loadFilmLoopDataD5: SCVW header:");
-		stream.hexdump(framesOffset - 8);
-	}
-	stream.skip(6);
-	uint16 channelSize = kSprChannelSizeD5;
-	stream.readUint16BE(); // should be kSprChannelSizeD5 = 24!
-	stream.skip(framesOffset - 16);
-
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE();
-		if (frameSize == 0) {
-			continue;
-		}
-		frameSize -= 2;
-		if (debugChannelSet(5, kDebugLoading)) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD5: Frame entry:");
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			uint16 msgWidth = stream.readUint16BE();
-			uint16 order = stream.readUint16BE();
-			frameSize -= 4;
-
-			int channel = order / channelSize;
-			int channelOffset = order % channelSize;
-			int offset = order;
-
-			debugC(8, kDebugLoading, "loadFilmLoopDataD5: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-
-			uint16 segSize = msgWidth;
-			uint16 nextStart = (channel + 1) * kSprChannelSizeD5;
-
-			while (segSize > 0) {
-				Sprite sprite(nullptr);
-				sprite._movie = g_director->getCurrentMovie();
-				if (newFrame.sprites.contains(channel)) {
-					sprite = newFrame.sprites.getVal(channel);
-				}
-
-				sprite._stretch = true;
-
-				uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
-				int startPosition = stream.pos() - channelOffset;
-				int finishPosition = stream.pos() + needSize;
-				readSpriteDataD5(stream, sprite, startPosition, finishPosition);
-				// Swap castLib ID value of -1 for the film loop's castLib ID
-				if (sprite._castId.castLib == -1)
-					sprite._castId.castLib = _cast->_castLibID;
-				if (sprite._scriptId.castLib == -1)
-					sprite._scriptId.castLib = _cast->_castLibID;
-				newFrame.sprites.setVal(channel, sprite);
-				segSize -= needSize;
-				offset += needSize;
-				channel += 1;
-				channelOffset = 0;
-				nextStart += kSprChannelSizeD5;
-			}
-
-			frameSize -= msgWidth;
-		}
-
-		for (auto &s : newFrame.sprites) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD5: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-					s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-					s._value._width, s._value._height);
-
-			if (s._key == -1) {
-				debugC(5, kDebugLoading, "loadFilmLoopDataD5: Skipping channel -1");
-				if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
-						 (s._value._height != -256 && s._value._height != 0))
-					warning("BUILDBOT: loadFilmLoopDataD5: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-						s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-						s._value._width, s._value._height);
-				continue;
-			}
-
-			s._value.setCast(s._value._castId);
-			Common::Point topLeft = s._value._startPoint;
-			if (s._value._cast) {
-				topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
-			}
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s._value._width,
-				topLeft.y + s._value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "loadFilmLoopDataD5: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "loadFilmLoopDataD5: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-}
-
-void FilmLoopCastMember::loadFilmLoopDataD6(Common::SeekableReadStreamEndian &stream) {
-	_initialRect = Common::Rect();
-	_frames.clear();
-
-	uint32 size = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "loadFilmLoopDataD6: SCVW body:");
-		uint32 pos = stream.pos();
-		stream.seek(0);
-		stream.hexdump(size);
-		stream.seek(pos);
-	}
-	uint32 framesOffset = stream.readUint32BE();
-	if (debugChannelSet(5, kDebugLoading)) {
-		debugC(5, kDebugLoading, "loadFilmLoopDataD6: SCVW header:");
-		stream.hexdump(framesOffset - 8);
-	}
-	stream.skip(6);
-	uint16 channelSize = kSprChannelSizeD6;
-	stream.readUint16BE(); // should be kSprChannelSizeD6 = 24!
-	stream.skip(framesOffset - 16);
-
-	FilmLoopFrame newFrame;
-
-	while (stream.pos() < size) {
-		uint16 frameSize = stream.readUint16BE();
-		if (frameSize == 0) {
-			continue;
-		}
-		frameSize -= 2;
-		if (debugChannelSet(5, kDebugLoading)) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD6: Frame entry:");
-			stream.hexdump(frameSize);
-		}
-
-		while (frameSize > 0) {
-			uint16 msgWidth = stream.readUint16BE();
-			uint16 order = stream.readUint16BE();
-			frameSize -= 4;
-
-			int channel = order / channelSize;
-			int channelOffset = order % channelSize;
-			int offset = order;
-
-			debugC(8, kDebugLoading, "loadFilmLoopDataD6: Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset);
-			if (debugChannelSet(8, kDebugLoading)) {
-				stream.hexdump(msgWidth);
-			}
-
-			uint16 segSize = msgWidth;
-			uint16 nextStart = (channel + 1) * kSprChannelSizeD4;
-
-			while (segSize > 0) {
-				Sprite sprite(nullptr);
-				sprite._movie = g_director->getCurrentMovie();
-				if (newFrame.sprites.contains(channel)) {
-					sprite = newFrame.sprites.getVal(channel);
-				}
-
-				sprite._stretch = true;
-
-				uint16 needSize = MIN((uint16)(nextStart - offset), segSize);
-				int startPosition = stream.pos() - channelOffset;
-				int finishPosition = stream.pos() + needSize;
-				readSpriteDataD6(stream, sprite, startPosition, finishPosition);
-				// Swap castLib ID value of -1 for the film loop's castLib ID
-				if (sprite._castId.castLib == -1)
-					sprite._castId.castLib = _cast->_castLibID;
-				if (sprite._scriptId.castLib == -1)
-					sprite._scriptId.castLib = _cast->_castLibID;
-				newFrame.sprites.setVal(channel, sprite);
-				segSize -= needSize;
-				offset += needSize;
-				channel += 1;
-				channelOffset = 0;
-				nextStart += kSprChannelSizeD6;
-			}
-
-			frameSize -= msgWidth;
-		}
-
-		for (auto &s : newFrame.sprites) {
-			debugC(5, kDebugLoading, "loadFilmLoopDataD6: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-					s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-					s._value._width, s._value._height);
-
-			if (s._key == -1) {
-				debugC(5, kDebugLoading, "loadFilmLoopDataD6: Skipping channel -1");
-				if (s._value._startPoint.x != 0 || s._value._startPoint.y != 0 || s._value._width != 0 ||
-						 (s._value._height != -256 && s._value._height != 0))
-					warning("BUILDBOT: loadFilmLoopDataD6: Malformed VWSC resource: Sprite: channel %d, castId %s, bbox %d %d %d %d", s._key,
-						s._value._castId.asString().c_str(), s._value._startPoint.x, s._value._startPoint.y,
-						s._value._width, s._value._height);
-				continue;
-			}
-
-			s._value.setCast(s._value._castId);
-			Common::Point topLeft = s._value._startPoint;
-			if (s._value._cast) {
-				topLeft -= s._value._cast->getRegistrationOffset(s._value._width, s._value._height);
-			}
-			Common::Rect spriteBbox(
-				topLeft.x,
-				topLeft.y,
-				topLeft.x + s._value._width,
-				topLeft.y + s._value._height
-			);
-			if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) {
-				if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) {
-					_initialRect = spriteBbox;
-				} else {
-					_initialRect.extend(spriteBbox);
-				}
-			}
-			debugC(8, kDebugLoading, "loadFilmLoopDataD6: New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-
-		}
-
-		_frames.push_back(newFrame);
-
-	}
-	debugC(5, kDebugLoading, "loadFilmLoopDataD6: Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height());
-}
-
-#endif
-
 Common::String FilmLoopCastMember::formatInfo() {
 	return Common::String::format(
 		"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, frameCount: %d, subchannelCount: %d, enableSound: %d, looping: %d, crop: %d, center: %d",


Commit: bdfe5780a5f699f984c1c7952be3a9f5cb4568b4
    https://github.com/scummvm/scummvm/commit/bdfe5780a5f699f984c1c7952be3a9f5cb4568b4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Completely disable Lingo in filmloops

Changed paths:
    engines/director/castmember/filmloop.cpp
    engines/director/movie.cpp
    engines/director/score.cpp
    engines/director/score.h


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index f069ea0ce0e..962b0e2a007 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -233,7 +233,7 @@ void FilmLoopCastMember::load() {
 
 	if (loop) {
 		debugC(2, kDebugLoading, "****** FilmLoopCastMember::load(): Loading '%s' id: %d, %d bytes", tag2str(tag), filmLoopId, (int)loop->size());
-		_score = new Score(g_director->getCurrentMovie());
+		_score = new Score(g_director->getCurrentMovie(), false);
 		_score->loadFrames(*loop, _cast->_version, true);
 		delete loop;
 	} else {
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index b840a3a4770..8f0f978527b 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -82,7 +82,7 @@ Movie::Movie(Window *window) {
 	_cast = new Cast(this, DEFAULT_CAST_LIB);
 	_casts.setVal(_cast->_castLibID, _cast);
 	_sharedCast = nullptr;
-	_score = new Score(this);
+	_score = new Score(this, true);
 
 	_selEnd = -1;
 	_selStart = -1;
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index e2a31dd17ae..0b81080537a 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -55,11 +55,12 @@ namespace Director {
 
 #include "director/palette-fade.h"
 
-Score::Score(Movie *movie) {
+Score::Score(Movie *movie, bool haveInteractivity) {
 	_movie = movie;
 	_window = movie->getWindow();
 	_vm = _movie->getVM();
 	_lingo = _vm->getLingo();
+	_haveInteractivity = haveInteractivity;
 
 	_soundManager = _window->getSoundManager();
 
@@ -329,6 +330,9 @@ void Score::startPlay() {
 		return;
 	}
 
+	if (_haveInteractivity && _version >= kFileVer300)
+		_movie->processEvent(kEventStartMovie);
+
 	// load first frame (either 1 or _nextFrame)
 	updateCurrentFrame();
 
@@ -351,23 +355,25 @@ void Score::step() {
 	if (_playState == kPlayStopped)
 		return;
 
-	if (!_movie->_inputEventQueue.empty() && !_window->frozenLingoStateCount()) {
-		_lingo->processEvents(_movie->_inputEventQueue, true);
-	}
-	if (_version >= kFileVer300 && !_window->_newMovieStarted && _playState != kPlayStopped) {
-		_movie->processEvent(kEventIdle);
+	if (_haveInteractivity) {
+		if (!_movie->_inputEventQueue.empty() && !_window->frozenLingoStateCount()) {
+			_lingo->processEvents(_movie->_inputEventQueue, true);
+		}
+		if (_version >= kFileVer300 && !_window->_newMovieStarted && _playState != kPlayStopped) {
+			_movie->processEvent(kEventIdle);
 
-		if (_version >= kFileVer600) {
-			if (_movie->_currentHoveredSpriteId) {
-				_movie->processEvent(kEventMouseWithin, _movie->_currentHoveredSpriteId);
-			}
+			if (_version >= kFileVer600) {
+				if (_movie->_currentHoveredSpriteId) {
+					_movie->processEvent(kEventMouseWithin, _movie->_currentHoveredSpriteId);
+				}
 
-			_soundManager->processCuePoints();
-		} else 	if (_version >= kFileVer500) {
-			// In D5, these events are only generated if a mouse button is pressed
-			bool isButtonDown = !Director::DT::isMouseInputIgnored() && (g_system->getEventManager()->getButtonState() != 0);
-			if (_movie->_currentHoveredSpriteId && isButtonDown) {
-				_movie->processEvent(kEventMouseWithin, _movie->_currentHoveredSpriteId);
+				_soundManager->processCuePoints();
+			} else 	if (_version >= kFileVer500) {
+				// In D5, these events are only generated if a mouse button is pressed
+				bool isButtonDown = !Director::DT::isMouseInputIgnored() && (g_system->getEventManager()->getButtonState() != 0);
+				if (_movie->_currentHoveredSpriteId && isButtonDown) {
+					_movie->processEvent(kEventMouseWithin, _movie->_currentHoveredSpriteId);
+				}
 			}
 		}
 	}
@@ -393,9 +399,13 @@ void Score::stopPlay() {
 	if (_stopPlayCalled)
 		return;
 	_stopPlayCalled = true;
-	if (_version >= kFileVer300)
-		_movie->processEvent(kEventStopMovie);
-	_lingo->executePerFrameHook(-1, 0);
+
+	if (_haveInteractivity) {
+		if (_version >= kFileVer300)
+			_movie->processEvent(kEventStopMovie);
+
+		_lingo->executePerFrameHook(-1, 0);
+	}
 }
 
 void Score::setDelay(uint32 ticks) {
@@ -661,7 +671,7 @@ void Score::update() {
 		// When Lingo::func_goto* is called, _nextFrame is set
 		// and _skipFrameAdvance is set to true.
 		// exitFrame is not called in this case.
-		if (!_window->_skipFrameAdvance && !_exitFrameCalled) {
+		if (_haveInteractivity && !_window->_skipFrameAdvance && !_exitFrameCalled) {
 			// Exit the current frame. This can include scopeless ScoreScripts.
 			_movie->processEvent(kEventExitFrame);
 			_exitFrameCalled = true;
@@ -708,64 +718,68 @@ void Score::update() {
 	debugC(1, kDebugEvents, "##############################");
 	g_debugger->frameHook();
 
-	// movie could have been stopped by a window switch or a debug flag
-	if (_playState == kPlayStopped) {
-		processFrozenScripts();
-		return;
-	}
+	uint32 count = 0;
+
+	if (_haveInteractivity) {
+		// movie could have been stopped by a window switch or a debug flag
+		if (_playState == kPlayStopped) {
+			processFrozenScripts();
+			return;
+		}
 
-	uint32 count = _window->frozenLingoStateCount();
+		count = _window->frozenLingoStateCount();
 
-	// Director 4 and below will allow infinite recursion via the perFrameHook.
-	if (_version < kFileVer500) {
-		// new frame, first call the perFrameHook (if one exists)
-		if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
-			// Call the perFrameHook as soon as a frame switch is done.
-			// If there is a transition, the perFrameHook is called
-			// after each transition subframe instead of here.
-			if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
-				_lingo->executePerFrameHook(_curFrameNumber, 0);
+		// Director 4 and below will allow infinite recursion via the perFrameHook.
+		if (_version < kFileVer500) {
+			// new frame, first call the perFrameHook (if one exists)
+			if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
+				// Call the perFrameHook as soon as a frame switch is done.
+				// If there is a transition, the perFrameHook is called
+				// after each transition subframe instead of here.
+				if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
+					_lingo->executePerFrameHook(_curFrameNumber, 0);
+				}
 			}
+			if (_window->frozenLingoStateCount() > count)
+				return;
 		}
-		if (_window->frozenLingoStateCount() > count)
-			return;
-	}
 
-	// Check to see if we've hit the recursion limit
-	if (_version >= kFileVer400 && _window->frozenLingoRecursionCount() >= 2) {
-		debugC(1, kDebugEvents, "Score::update(): hitting D4 recursion depth limit, defrosting");
-		processFrozenScripts(true);
-		return;
-	} else if (_window->frozenLingoStateCount() >= 64) {
-		warning("Score::update(): Stopping runaway script recursion. By this point D3 will have run out of stack space");
-		processFrozenScripts();
-		return;
-	}
+		// Check to see if we've hit the recursion limit
+		if (_version >= kFileVer400 && _window->frozenLingoRecursionCount() >= 2) {
+			debugC(1, kDebugEvents, "Score::update(): hitting D4 recursion depth limit, defrosting");
+			processFrozenScripts(true);
+			return;
+		} else if (_window->frozenLingoStateCount() >= 64) {
+			warning("Score::update(): Stopping runaway script recursion. By this point D3 will have run out of stack space");
+			processFrozenScripts();
+			return;
+		}
 
-	// Director 5 and above actually check for recursion for the perFrameHook.
-	if (_version >= kFileVer500) {
-		// new frame, first call the perFrameHook (if one exists)
-		if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
-			// Call the perFrameHook as soon as a frame switch is done.
-			// If there is a transition, the perFrameHook is called
-			// after each transition subframe instead of here.
-			//
-			// This also sends stepFrame message to actorList
-			if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
-				_lingo->executePerFrameHook(_curFrameNumber, 0);
+		// Director 5 and above actually check for recursion for the perFrameHook.
+		if (_version >= kFileVer500) {
+			// new frame, first call the perFrameHook (if one exists)
+			if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
+				// Call the perFrameHook as soon as a frame switch is done.
+				// If there is a transition, the perFrameHook is called
+				// after each transition subframe instead of here.
+				//
+				// This also sends stepFrame message to actorList
+				if (_currentFrame->_mainChannels.transType == 0 && _currentFrame->_mainChannels.trans.isNull()) {
+					_lingo->executePerFrameHook(_curFrameNumber, 0);
+				}
 			}
+			if (_window->frozenLingoStateCount() > count)
+				return;
 		}
-		if (_window->frozenLingoStateCount() > count)
-			return;
-	}
 
-	if (_version >= kFileVer600) {
-		bool prevDis = _disableGoPlayUpdateStage;
-		_disableGoPlayUpdateStage = true;
+		if (_version >= kFileVer600) {
+			bool prevDis = _disableGoPlayUpdateStage;
+			_disableGoPlayUpdateStage = true;
 
-		_movie->broadcastEvent(kEventPrepareFrame);
+			_movie->broadcastEvent(kEventPrepareFrame);
 
-		_disableGoPlayUpdateStage = prevDis;
+			_disableGoPlayUpdateStage = prevDis;
+		}
 	}
 
 	bool sound1Changed = true;
@@ -787,6 +801,9 @@ void Score::update() {
 	renderFrame(_curFrameNumber, kRenderModeNormal, sound1Changed, sound2Changed);
 	_window->_newMovieStarted = false;
 
+	if (!_haveInteractivity)
+		return;
+
 	// then call the stepMovie hook (if one exists)
 	// D4 and above only call it if _allowOutdatedLingo is enabled.
 	count = _window->frozenLingoStateCount();
diff --git a/engines/director/score.h b/engines/director/score.h
index 33329321bd9..0e5a7c03d8c 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -65,7 +65,7 @@ struct Label {
 
 class Score {
 public:
-	Score(Movie *movie);
+	Score(Movie *movie, bool haveInteractivity);
 	~Score();
 
 	Movie *getMovie() const { return _movie; }
@@ -236,6 +236,8 @@ public:
 
 	bool _disableGoPlayUpdateStage;
 
+	bool _haveInteractivity;
+
 private:
 	DirectorEngine *_vm;
 	Lingo *_lingo;


Commit: e720eaf75509167a3e77df724fa1149e45d5b65e
    https://github.com/scummvm/scummvm/commit/e720eaf75509167a3e77df724fa1149e45d5b65e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Let Filmloop castmember Score keep its own state

Changed paths:
    engines/director/castmember/filmloop.cpp
    engines/director/score.cpp


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index 962b0e2a007..fa0c8c4d7ee 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -117,8 +117,8 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 
 	// get the list of sprite IDs for this frame
 	Common::Array<int> spriteIds;
-	for (uint i = 0; i < _score->_scoreCache[frame]->_sprites.size(); ++i) {
-		if (_score->_scoreCache[frame]->_sprites[i] && !_score->_scoreCache[frame]->_sprites[i]->_castId.isNull())
+	for (uint i = 0; i < _score->_channels.size(); ++i) {
+		if (_score->_channels[i]->_sprite && !_score->_channels[i]->_sprite->_castId.isNull())
 			spriteIds.push_back(i);
 	}
 
@@ -133,7 +133,7 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 
 	// copy the sprites in order to the list
 	for (auto &iter : spriteIds) {
-		Sprite *src = _score->_scoreCache[frame]->_sprites[iter];
+		Sprite *src = _score->_channels[iter]->_sprite;
 		if (src->_castId.isNull())
 			continue;
 		// translate sprite relative to the global bounding box
@@ -162,6 +162,7 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 		Channel chan(nullptr, src);
 		_subchannels.push_back(chan);
 	}
+
 	// Initialise the widgets on all of the subchannels.
 	// This has to be done once the list has been constructed, otherwise
 	// the list grow operation will erase the widgets as they aren't
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 0b81080537a..7d847451068 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -164,6 +164,9 @@ bool Score::processFrozenPlayScript() {
 
 
 bool Score::processFrozenScripts(bool recursion, int count) {
+	if (!_haveInteractivity)
+		return true;
+
 	if (!processFrozenPlayScript())
 		return false;
 
@@ -928,11 +931,11 @@ void Score::incrementFilmLoops() {
 			if (!fl->_score->_scoreCache.empty()) {
 				// increment the film loop counter
 				if (fl->_looping) {
-					it->_filmLoopFrame += 1;
-					it->_filmLoopFrame %= fl->_score->_scoreCache.size();
-				} else if (it->_filmLoopFrame < (fl->_score->_scoreCache.size() - 1)) {
-					it->_filmLoopFrame += 1;
+					if (_curFrameNumber + 1 > getFramesNum())
+						fl->_score->setCurrentFrame(1);
 				}
+
+				fl->_score->step();
 			} else {
 				warning("Score::updateFilmLoops(): invalid film loop in castId %s", it->_sprite->_castId.asString().c_str());
 			}


Commit: 828966aeb749b691dfd0634eae6a045fc75ae58b
    https://github.com/scummvm/scummvm/commit/828966aeb749b691dfd0634eae6a045fc75ae58b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Streamlined filmloop sprites passing

Changed paths:
    engines/director/castmember/filmloop.cpp
    engines/director/score.cpp


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index fa0c8c4d7ee..13a20f0edd6 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -131,35 +131,45 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 			bbox.top + bbox.height()/2,
 			bbox.width(), bbox.height());
 
+	bool needToScale = (bbox.width() != _initialRect.width() || bbox.height() != _initialRect.height());
+	float scaleX = 1.0f;
+	float scaleY = 1.0f;
+
+	if (needToScale) {
+		scaleX = (float)bbox.width() / _initialRect.width();
+		scaleY = (float)bbox.height() / _initialRect.height();
+	}
+
 	// copy the sprites in order to the list
 	for (auto &iter : spriteIds) {
-		Sprite *src = _score->_channels[iter]->_sprite;
-		if (src->_castId.isNull())
+		Sprite src = *_score->_channels[iter]->_sprite;
+		if (src._castId.isNull())
 			continue;
+
+		debugCN(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): sprite: %d - cast: %s, orig: %d,%d %dx%d",
+				iter, src._castId.asString().c_str(),
+				src._startPoint.x, src._startPoint.y, src._width, src._height);
+
 		// translate sprite relative to the global bounding box
-		int16 relX = (src->_startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width();
-		int16 relY = (src->_startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height();
-		int16 absX = relX + bbox.left;
-		int16 absY = relY + bbox.top;
-		int16 width = src->_width * widgetRect.width() / _initialRect.width();
-		int16 height = src->_height * widgetRect.height() / _initialRect.height();
-
-		debugC(5, kDebugImages, "FilmLoopCastMember::getSubChannels(): sprite: %d - cast: %s, orig: %d,%d %dx%d, trans: %d,%d %dx%d",
-				iter, src->_castId.asString().c_str(),
-				src->_startPoint.x, src->_startPoint.y, src->_width, src->_height,
-				absX, absY, width, height);
-
-		// Re-inject the translated position into the Sprite.
-		// This saves the hassle of having to force the Channel to be in puppet mode.
-		src->_width = width;
-		src->_height = height;
-		src->_startPoint = Common::Point(absX, absY);
-		src->_stretch = true;
+		if (needToScale) {
+			src._startPoint.x = (src._startPoint.x - _initialRect.left) * scaleX + bbox.left;
+			src._startPoint.y = (src._startPoint.y - _initialRect.top) * scaleY + bbox.top;
+			src._width = widgetRect.width();
+			src._height = widgetRect.height();
+			src._stretch = true;
+
+			debugCN(5, kDebugImages, ", scaled: %d,%d %dx%d", src._startPoint.x, src._startPoint.y, src._width, src._height);
+		} else {
+			src._startPoint.x += bbox.left;
+			src._startPoint.y += bbox.top;
+
+			debugCN(5, kDebugImages, ", no scaling");
+		}
 
 		// Film loop frames are constructed as a series of Channels, much like how a normal frame
 		// is rendered by the Score. We don't include a pointer to the current Score here,
 		// that's only for querying the constraint channel which is not used.
-		Channel chan(nullptr, src);
+		Channel chan(nullptr, &src);
 		_subchannels.push_back(chan);
 	}
 
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 7d847451068..ca8623760a9 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -799,14 +799,14 @@ void Score::update() {
 	}
 
 	_firstRun = false;
-
-	// Window is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100)
-	renderFrame(_curFrameNumber, kRenderModeNormal, sound1Changed, sound2Changed);
 	_window->_newMovieStarted = false;
 
 	if (!_haveInteractivity)
 		return;
 
+	// Window is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100)
+	renderFrame(_curFrameNumber, kRenderModeNormal, sound1Changed, sound2Changed);
+
 	// then call the stepMovie hook (if one exists)
 	// D4 and above only call it if _allowOutdatedLingo is enabled.
 	count = _window->frozenLingoStateCount();


Commit: 13836af96c200771343b938cd5a70fdc214306bf
    https://github.com/scummvm/scummvm/commit/13836af96c200771343b938cd5a70fdc214306bf
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Filmloop: Fix filmloop scaling bug

Changed paths:
    engines/director/castmember/filmloop.cpp


diff --git a/engines/director/castmember/filmloop.cpp b/engines/director/castmember/filmloop.cpp
index 13a20f0edd6..fd851a4d44a 100644
--- a/engines/director/castmember/filmloop.cpp
+++ b/engines/director/castmember/filmloop.cpp
@@ -160,8 +160,8 @@ Common::Array<Channel> *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, u
 
 			debugCN(5, kDebugImages, ", scaled: %d,%d %dx%d", src._startPoint.x, src._startPoint.y, src._width, src._height);
 		} else {
-			src._startPoint.x += bbox.left;
-			src._startPoint.y += bbox.top;
+			src._startPoint.x = (src._startPoint.x - _initialRect.left) + bbox.left;
+			src._startPoint.y = (src._startPoint.y - _initialRect.top) + bbox.top;
 
 			debugCN(5, kDebugImages, ", no scaling");
 		}


Commit: b0f735987622de32ee3bb471a5e0895541d05927
    https://github.com/scummvm/scummvm/commit/b0f735987622de32ee3bb471a5e0895541d05927
Author: ramyak-sharma (ramyaksharma1 at gmail.com)
Date: 2026-04-09T12:51:31+02:00

Commit Message:
DIRECTOR: Fix crash when setting picture of unloaded bitmap cast member

Changed paths:
    engines/director/castmember/bitmap.cpp


diff --git a/engines/director/castmember/bitmap.cpp b/engines/director/castmember/bitmap.cpp
index 726534b1c90..6fa40e136b2 100644
--- a/engines/director/castmember/bitmap.cpp
+++ b/engines/director/castmember/bitmap.cpp
@@ -409,6 +409,8 @@ Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel
 }
 
 Graphics::Surface *BitmapCastMember::getDitherImg() {
+	if (!_picture->_surface.getPixels())
+        return nullptr;
 	Graphics::Surface *dither = nullptr;
 
 	// Convert indexed image to indexed palette




More information about the Scummvm-git-logs mailing list