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

bluegr noreply at scummvm.org
Fri Jul 3 06:32:18 UTC 2026


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

Summary:
ca8ec48109 NANCY: NANCY10: Fix reading data for SecondaryVideo ARs
59a2d1830c NANCY: NANCY11: Implement changes to OrderingPuzzle
dcecf3935c NANCY: NANCY11: Further work on CardGamePuzzle
86d338dab0 NANCY: NANCY12: More work on the ActionZone fields
5052525d1a NANCY: NANCY10: Use the correct screen rect in the cellphone browser
e697caa6c8 NANCY: NANCY12: Fix light beam drawing in MirrorLightPuzzle


Commit: ca8ec48109e9cf4e553f9a030371f449cd534f4b
    https://github.com/scummvm/scummvm/commit/ca8ec48109e9cf4e553f9a030371f449cd534f4b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:04+03:00

Commit Message:
NANCY: NANCY10: Fix reading data for SecondaryVideo ARs

This fixes SecondaryVideo ARs going out of sync in Nancy11+ and reading
junk data. This allows us to remove a relevant hack for Nancy10

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


diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index c504e75271e..2e4701d9952 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -312,17 +312,6 @@ void PlaySecondaryMovie::onPause(bool pause) {
 void PlaySecondaryMovie::execute() {
 	switch (_state) {
 	case kBegin:
-		// HACK: In Nancy 10, scene 2987, there are two PlaySecondaryMovie records that play
-		// the same video, but have no dependencies. The first video leads to the losing scene,
-		// while the second one leads to the winning scene. Since none of the two records has a
-		// dependency, the first one will always be executed. It seems like there should be
-		// a check to prevent the first record from being executed, but it wasn't possible to
-		// find it. Don't start the first record for now, so that the second one can be
-		// executed and the player can proceed.
-		// TODO: Find out what the original engine does in this case, and implement it properly.
-		if (g_nancy->getGameType() == kGameTypeNancy10 && _videoSceneChange == kMovieSceneChange && _sceneChange.sceneID == 2989)
-			return;
-
 		init();
 		registerGraphics();
 		g_nancy->_sound->loadSound(_sound);
diff --git a/engines/nancy/action/secondaryvideo.cpp b/engines/nancy/action/secondaryvideo.cpp
index 25ad754dfd2..1c4491cad30 100644
--- a/engines/nancy/action/secondaryvideo.cpp
+++ b/engines/nancy/action/secondaryvideo.cpp
@@ -207,7 +207,7 @@ void PlaySecondaryVideo::readData(Common::SeekableReadStream &stream) {
 
 	_videoDescs.resize(numVideoDescs);
 	for (uint i = 0; i < numVideoDescs; ++i) {
-		_videoDescs[i].readData(stream);
+		_videoDescs[i].readData(stream, true);
 	}
 
 	// Nancy 10+ stores the record's dependency block right after the video
diff --git a/engines/nancy/commontypes.cpp b/engines/nancy/commontypes.cpp
index 219a1c0b1da..703daebc2e0 100644
--- a/engines/nancy/commontypes.cpp
+++ b/engines/nancy/commontypes.cpp
@@ -128,12 +128,16 @@ void MultiEventFlagDescription::execute() {
 	}
 }
 
-void SecondaryVideoDescription::readData(Common::SeekableReadStream &stream) {
+void SecondaryVideoDescription::readData(Common::SeekableReadStream &stream, bool hasTrailingRects) {
 	frameID = stream.readUint16LE();
 	readRect(stream, srcRect);
 	readRect(stream, destRect);
-	if (g_nancy->getGameType() <= kGameTypeNancy10)
-		stream.skip(0x20);
+	// PlaySecondaryVideo keeps the two trailing rects in every game, while
+	// PlaySecondaryMovie only has them up to Nancy 9 and dropped them afterwards.
+	if (hasTrailingRects || g_nancy->getGameType() <= kGameTypeNancy9) {
+		stream.skip(16);
+		stream.skip(16);
+	}
 }
 
 void SoundEffectDescription::readData(Common::SeekableReadStream &stream) {
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 004c3b43387..65833886434 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -210,7 +210,7 @@ struct SecondaryVideoDescription {
 	Common::Rect destRect;
 	// 2 unknown/empty rects
 
-	void readData(Common::SeekableReadStream &stream);
+	void readData(Common::SeekableReadStream &stream, bool hasTrailingRects = false);
 };
 
 // Describes set of effects that can be applied to sounds.


Commit: 59a2d1830c01da04508de12a75882a09c58b036b
    https://github.com/scummvm/scummvm/commit/59a2d1830c01da04508de12a75882a09c58b036b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:05+03:00

Commit Message:
NANCY: NANCY11: Implement changes to OrderingPuzzle

Fixes the Zodiac symbols puzzle

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


diff --git a/engines/nancy/action/puzzle/orderingpuzzle.cpp b/engines/nancy/action/puzzle/orderingpuzzle.cpp
index 766530b558e..d09fecf70b3 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -85,6 +85,11 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 		ser.syncAsUint16LE(numElements);
 	}
 
+	// Nancy 11 bumped the base ordering puzzle's element cap from 15 to 16
+	if (_puzzleType == kOrdering && g_nancy->getGameType() >= kGameTypeNancy11) {
+		maxNumElements = 16;
+	}
+
 	switch (_puzzleType) {
 	case kOrderItems :
 		ser.syncAsByte(_hasSecondState);


Commit: dcecf3935c84f18f58bb1737c834b8c6bd92b509
    https://github.com/scummvm/scummvm/commit/dcecf3935c84f18f58bb1737c834b8c6bd92b509
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:06+03:00

Commit Message:
NANCY: NANCY11: Further work on CardGamePuzzle

Makes the "Go fish" card game puzzle with Jane playable

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


diff --git a/engines/nancy/action/puzzle/cardgamepuzzle.cpp b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
index d1c3ab927fb..c536ae4e185 100644
--- a/engines/nancy/action/puzzle/cardgamepuzzle.cpp
+++ b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
@@ -456,6 +456,39 @@ void CardGamePuzzle::execute() {
 	// The deal/match slide animations and the voiced lines come in a later stage.
 }
 
+// A column is playable while the player (side 1) owns one or two cards in it - a column they are
+// building but have not yet completed. Depending on the scene the click target is either the bottom
+// button row or the player's own cards in the tableau.
+int CardGamePuzzle::columnUnderMouse(const Common::Point &mousePos) const {
+	if (usesColumnButtons()) {
+		for (int col = 0; col < (int)_numCols; ++col) {
+			if (_board[1].colCount[col] <= 0 || _board[1].colCount[col] >= 3) {
+				continue;
+			}
+
+			Common::Rect button = NancySceneState.getViewport().convertViewportToScreen(_columnButtons[col]);
+			if (button.contains(mousePos)) {
+				return col;
+			}
+		}
+	} else {
+		for (int row = 0; row < _numRows; ++row) {
+			for (int col = 0; col < (int)_numCols; ++col) {
+				if (_board[1].grid[row][col] != 1 || _board[1].colCount[col] >= 3) {
+					continue;
+				}
+
+				Common::Rect card = NancySceneState.getViewport().convertViewportToScreen(_faceDownSrc[row * kMaxCols + col]);
+				if (card.contains(mousePos)) {
+					return col;
+				}
+			}
+		}
+	}
+
+	return -1;
+}
+
 void CardGamePuzzle::handleInput(NancyInput &input) {
 	if (_state != kRun || _awaitingEnd) {
 		return;
@@ -475,46 +508,27 @@ void CardGamePuzzle::handleInput(NancyInput &input) {
 		return;
 	}
 
-	int opponent = _currentTurn ^ 1;
-
-	for (int col = 0; col < (int)_numCols; ++col) {
-		Common::Rect button = NancySceneState.getViewport().convertViewportToScreen(_columnButtons[col]);
-		if (!button.contains(input.mousePos)) {
-			continue;
-		}
-
-		// A column is playable only while the opponent still holds a card in it
-		bool hasTarget = false;
-		for (int row = 0; row < _numRows; ++row) {
-			if (_board[opponent].grid[row][col] == 1) {
-				hasTarget = true;
-				break;
-			}
-		}
-
-		if (!hasTarget) {
-			return;
-		}
+	int col = columnUnderMouse(input.mousePos);
+	if (col == -1) {
+		return;
+	}
 
-		g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+	g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp) {
-			bool before[kMaxRows][kMaxCols];
-			for (int r = 0; r < kMaxRows; ++r)
-				for (int c = 0; c < kMaxCols; ++c)
-					before[r][c] = (_board[1].grid[r][c] == 1);
+	if (input.input & NancyInput::kLeftMouseButtonUp) {
+		bool before[kMaxRows][kMaxCols];
+		for (int r = 0; r < kMaxRows; ++r)
+			for (int c = 0; c < kMaxCols; ++c)
+				before[r][c] = (_board[1].grid[r][c] == 1);
 
-			playVoice(_moveVoiceName);
-			playColumn(col);
-			finishMove();
+		playVoice(_moveVoiceName);
+		playColumn(col);
+		finishMove();
 
-			// finishMove may have ended the game (kActionTrigger); only animate if still playing
-			if (_state == kRun) {
-				startMoveAnimation(before);
-			}
+		// finishMove may have ended the game (kActionTrigger); only animate if still playing
+		if (_state == kRun) {
+			startMoveAnimation(before);
 		}
-
-		return;
 	}
 }
 
diff --git a/engines/nancy/action/puzzle/cardgamepuzzle.h b/engines/nancy/action/puzzle/cardgamepuzzle.h
index a251884481c..98f758d1ece 100644
--- a/engines/nancy/action/puzzle/cardgamepuzzle.h
+++ b/engines/nancy/action/puzzle/cardgamepuzzle.h
@@ -63,6 +63,11 @@ protected:
 
 	bool dealOne(int player); // draw a card from the deck to a side; false if the deck is empty
 	void drawBoard();
+	// True when the scene has a bottom button row (the player clicks those); false when it doesn't,
+	// in which case the player clicks their own cards in the tableau directly.
+	bool usesColumnButtons() const { return _columnButtons[0].top != _columnButtons[0].bottom; }
+	// The column the player is pointing at (owning 1-2 cards in it), or -1 if none is under the mouse.
+	int columnUnderMouse(const Common::Point &mousePos) const;
 	// Current side takes every opponent card in the given column; returns true if a card moved.
 	bool playColumn(int col);
 	int aiPickColumn();       // the AI's column choice, or -1 if it has no productive move


Commit: 86d338dab0548f3d5b3cb3f16ef5eb5ce9893c3c
    https://github.com/scummvm/scummvm/commit/86d338dab0548f3d5b3cb3f16ef5eb5ce9893c3c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:07+03:00

Commit Message:
NANCY: NANCY12: More work on the ActionZone fields

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


diff --git a/engines/nancy/action/actionzone.cpp b/engines/nancy/action/actionzone.cpp
index dc8f730137e..aecb02f585f 100644
--- a/engines/nancy/action/actionzone.cpp
+++ b/engines/nancy/action/actionzone.cpp
@@ -59,8 +59,9 @@ void ActionZone::readData(Common::SeekableReadStream &stream) {
 	case 0x13:
 		readSpecialEffect(stream);
 		break;
-	case 0x0b:
-		stream.skip(3);		// int16 + byte (plain, no peek)
+	case 0x0b:				// collision zone: event-flag id + on/off
+		tailId = stream.readSint16LE();
+		tailFlag = stream.readByte();
 		break;
 	case 0x0e:
 		stream.skip(2);		// int16
@@ -80,9 +81,10 @@ void ActionZone::readData(Common::SeekableReadStream &stream) {
 	case 2:
 		stream.skip(24);	// Rect + int32 + int16 + int16
 		break;
-	case 0x0c:
+	case 0x0c:				// trigger zone: special effect + target scene id + flag
 		readSpecialEffect(stream);
-		stream.skip(3);		// int16 + byte
+		tailId = stream.readSint16LE();
+		tailFlag = stream.readByte();
 		break;
 	case 0x15:
 		readSpecialEffect(stream);
diff --git a/engines/nancy/action/actionzone.h b/engines/nancy/action/actionzone.h
index 9d359e15647..f2711a5644a 100644
--- a/engines/nancy/action/actionzone.h
+++ b/engines/nancy/action/actionzone.h
@@ -63,6 +63,12 @@ struct ActionZone {
 	int32 specialEffect[5] = {};
 	byte specialEffectFlag = 0;
 
+	// int16 + byte trailer carried by the collision (0x0b) and trigger (0x0c)
+	// subtypes. For 0x0b it is an event-flag id + on/off; for 0x0c it is a target
+	// scene id + a flag. Left at their defaults for the other subtypes.
+	int16 tailId = 0;
+	byte tailFlag = 0;
+
 	// OverlayZone subtypes (0x0d / 0x16)
 	Common::String overlayName;
 	Common::Array<Common::Rect> overlaySrcRects;


Commit: 5052525d1a9b727a113f3bbcd367e87bbb28ddd4
    https://github.com/scummvm/scummvm/commit/5052525d1a9b727a113f3bbcd367e87bbb28ddd4
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:07+03:00

Commit Message:
NANCY: NANCY10: Use the correct screen rect in the cellphone browser

Fixes text being cut off in long search subjects

Changed paths:
    engines/nancy/ui/cellphonepopup.cpp


diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 2fbfda3301b..bc55160ffbc 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -1251,16 +1251,25 @@ Common::Rect CellPhonePopup::directoryRowRect(uint visibleIndex) const {
 	const Common::Rect &ws = _uiclData->welcomeScreen.destRect;
 	const int pitch = rowPitch();
 
+	// The web / email lists render under the zoomed (keypad-hidden) chrome,
+	// where the LCD extends into the wider emailListContainer. Use that as
+	// the right bound so long entries aren't clipped to the narrow
+	// keypad-mode screen; the directory list keeps the small LCD.
+	const Common::Rect &lcd =
+		(isZoomedChromeState() && !_uiclData->emailListContainer.isEmpty())
+			? _uiclData->emailListContainer
+			: ws;
+
 	// Row text spans from just right of the arrow cursor to a margin
-	// inside the LCD's right edge (the -30 the original applies).
+	// inside the LCD's right edge.
 	int xLeftScreen, xRightScreen;
 	if (!cursor.isEmpty()) {
 		xLeftScreen  = cursor.right + 5;
-		xRightScreen = ws.right - 30;
+		xRightScreen = lcd.right - 30;
 	} else {
 		const Common::Rect &arrow = _uiclData->dirArrowSrc;
 		xLeftScreen  = ws.left + arrow.width() + 4;
-		xRightScreen = ws.right - 2;
+		xRightScreen = lcd.right - 2;
 	}
 
 	const int yTopScreen = rowTopScreen() + (int)visibleIndex * pitch;


Commit: e697caa6c80011ed0b22d5051b893b7cddb2a6c9
    https://github.com/scummvm/scummvm/commit/e697caa6c80011ed0b22d5051b893b7cddb2a6c9
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-03T09:32:08+03:00

Commit Message:
NANCY: NANCY12: Fix light beam drawing in MirrorLightPuzzle

Changed paths:
    engines/nancy/action/puzzle/mirrorlightpuzzle.cpp
    engines/nancy/action/puzzle/mirrorlightpuzzle.h
    engines/nancy/ui/viewport.h


diff --git a/engines/nancy/action/puzzle/mirrorlightpuzzle.cpp b/engines/nancy/action/puzzle/mirrorlightpuzzle.cpp
index 25b563b095b..7e0f6fa0699 100644
--- a/engines/nancy/action/puzzle/mirrorlightpuzzle.cpp
+++ b/engines/nancy/action/puzzle/mirrorlightpuzzle.cpp
@@ -34,16 +34,18 @@ namespace Nancy {
 namespace Action {
 
 // TODO - open items for this puzzle:
-//  - Win/exit scene progression: the "solved" event flag is set outside the
-//    puzzle data (not embedded here) and is still unidentified, so the scene
-//    may not advance after a win.
-//  - No exit/cancel path: how the player quits an unsolved puzzle is unknown.
-//  - Win presentation: only a static lit-bulb overlay is drawn; the original
-//    plays a bulb-lights-up animation with a sound cue - neither is wired up.
+//  - Win/exit is fully EXTERNAL: the record has no scene-change of its own (its
+//    scene-change vtable slot is null) and writes no scene state, so the scene
+//    itself watches for the solved state and transitions. The exact signal (an
+//    event flag?) is still unidentified, so on solve we just finishExecution and
+//    the scene may not advance. The player's quit/cancel path is likewise unknown.
+//  - Win presentation: the original lights the bulb via a per-zone movie
+//    animation (loaded by the movie loader at init); we draw a single static
+//    overlay frame with no sound instead.
 //  - Detector zone is chosen heuristically (any non-boundary zone away from the
-//    source); the true detector zone is unconfirmed.
-//  - Mirror rotation step (2 deg/click) and the glowing light-ray rendering are
-//    approximations, not the original's exact behaviour.
+//    source); the precise detector is the tiny boundary zone inside the bulb.
+//  - Mirror rotation step (2 deg/click) approximates the original's exact
+//    per-click amount.
 
 void MirrorLightPuzzle::readData(Common::SeekableReadStream &stream) {
 	// 65-byte base header.
@@ -157,7 +159,13 @@ void MirrorLightPuzzle::traceBeam() {
 		}
 
 		if (hit != -1) {
-			_beamPath.push_back(p);
+			// Bounce at the mirror's centre (not the edge where the beam entered),
+			// so the beam visually meets each mirror at its middle.
+			const Common::Rect &mr = _mirrors[hit].destRect;
+			px = (mr.left + mr.right) / 2.0;
+			py = (mr.top + mr.bottom) / 2.0;
+			_beamPath.push_back(Common::Point((int16)px, (int16)py));
+
 			// Reflect about the mirror's surface normal (its stored angle).
 			double nx = cos(_mirrors[hit].angle);
 			double ny = -sin(_mirrors[hit].angle);
@@ -187,6 +195,94 @@ void MirrorLightPuzzle::traceBeam() {
 	_beamPath.push_back(Common::Point((int16)(px + 0.5), (int16)(py + 0.5)));
 }
 
+void MirrorLightPuzzle::drawBeamGlow() {
+	if (_beamPath.size() < 2) {
+		return;
+	}
+
+	const int w = _drawSurface.w;
+	const int h = _drawSurface.h;
+	// A soft Gaussian profile reads as a fuzzy, translucent light ray. peak is the
+	// additive intensity at the beam centre (well under 255 so the room shows
+	// through); sigma controls how quickly it fades; R clips the faint tail.
+	const int R = MAX<int>(4, _glowRadius + _glowRadius / 2);
+	const int R2 = R * R;
+	const double sigma = MAX<double>(1.5, _glowRadius / 2.0);
+	const double twoSigma2 = 2.0 * sigma * sigma;
+	const int peak = 110;
+
+	// Accumulate the max glow intensity per pixel (so overlapping stamps don't
+	// over-brighten), then composite once.
+	Common::Array<byte> intensity(w * h, 0);
+	for (uint s = 1; s < _beamPath.size(); ++s) {
+		const Common::Point &p0 = _beamPath[s - 1];
+		const Common::Point &p1 = _beamPath[s];
+		int dx = p1.x - p0.x;
+		int dy = p1.y - p0.y;
+		int len = MAX<int>(1, (int)sqrt((double)(dx * dx + dy * dy)));
+		for (int i = 0; i <= len; ++i) {
+			int cx = p0.x + dx * i / len;
+			int cy = p0.y + dy * i / len;
+			for (int oy = -R; oy <= R; ++oy) {
+				for (int ox = -R; ox <= R; ++ox) {
+					int px = cx + ox;
+					int py = cy + oy;
+					if (px < 0 || py < 0 || px >= w || py >= h) {
+						continue;
+					}
+					int d2 = ox * ox + oy * oy;
+					if (d2 > R2) {
+						continue;
+					}
+					int val = (int)(peak * exp(-(double)d2 / twoSigma2));
+					if (val <= 0) {
+						continue;
+					}
+					byte &acc = intensity[py * w + px];
+					if (val > acc) {
+						acc = (byte)val;
+					}
+				}
+			}
+		}
+	}
+
+	const Graphics::ManagedSurface &bg = NancySceneState.getViewport().getBackground();
+	const Graphics::PixelFormat &fmt = _drawSurface.format;
+	uint32 transColor = _drawSurface.getTransparentColor();
+
+	for (int y = 0; y < h; ++y) {
+		for (int x = 0; x < w; ++x) {
+			int inten = intensity[y * w + x];
+			if (inten == 0) {
+				continue;
+			}
+
+			// Skip actual mirror-sprite pixels (the mirror faces stay clean), but
+			// still glow over the transparent parts of a mirror's bounding box.
+			// Mirrors are the only thing drawn before the glow, so any opaque
+			// pixel here is a mirror pixel.
+			uint32 under = _drawSurface.getPixel(x, y);
+			if (under != transColor) {
+				continue;
+			}
+
+			// Glow over the scene background behind the beam.
+			byte r, g, b;
+			if (x < bg.w && y < bg.h) {
+				bg.format.colorToRGB(bg.getPixel(x, y), r, g, b);
+			} else {
+				r = g = b = 0;
+			}
+
+			r = (byte)MIN<int>(255, r + inten);
+			g = (byte)MIN<int>(255, g + inten);
+			b = (byte)MIN<int>(255, b + inten);
+			_drawSurface.setPixel(x, y, fmt.RGBToColor(r, g, b));
+		}
+	}
+}
+
 void MirrorLightPuzzle::redraw() {
 	_drawSurface.clear(g_nancy->_graphics->getTransColor());
 
@@ -195,34 +291,9 @@ void MirrorLightPuzzle::redraw() {
 		drawMirror(i);
 	}
 
-	// The beam as a round radial falloff (dim green-white outer -> bright white
-	// core) to read as glowing light. TODO: the original composites actual
-	// glowing light-ray sprites; a soft/additive glow is a follow-up.
-	if (_beamPath.size() >= 2) {
-		int t = MAX<int>(2, _glowRadius);
-		uint32 colors[3] = {
-			_drawSurface.format.RGBToColor(90, 120, 90),	// outer haze
-			_drawSurface.format.RGBToColor(180, 210, 180),	// mid glow
-			_drawSurface.format.RGBToColor(255, 255, 255)	// core
-		};
-		int bands[3] = { t, t / 2, MAX<int>(1, t / 4) };
-		// Outer band first so brighter bands land on top.
-		for (int band = 0; band < 3; ++band) {
-			int r = bands[band];
-			for (uint i = 1; i < _beamPath.size(); ++i) {
-				const Common::Point &p0 = _beamPath[i - 1];
-				const Common::Point &p1 = _beamPath[i];
-				for (int oy = -r; oy <= r; ++oy) {
-					for (int ox = -r; ox <= r; ++ox) {
-						if (ox * ox + oy * oy > r * r) {
-							continue;
-						}
-						_drawSurface.drawLine(p0.x + ox, p0.y + oy, p1.x + ox, p1.y + oy, colors[band]);
-					}
-				}
-			}
-		}
-	}
+	// The beam, composited additively over the mirrors and scene background so it
+	// reads as glowing light rather than flat lines.
+	drawBeamGlow();
 
 	// Once solved, light up the detector overlay (the bulb graphic).
 	if (_solved) {
diff --git a/engines/nancy/action/puzzle/mirrorlightpuzzle.h b/engines/nancy/action/puzzle/mirrorlightpuzzle.h
index 9c935e09dbe..9f8fe0ce728 100644
--- a/engines/nancy/action/puzzle/mirrorlightpuzzle.h
+++ b/engines/nancy/action/puzzle/mirrorlightpuzzle.h
@@ -85,6 +85,7 @@ protected:
 	void drawMirror(uint index);
 	void rotateMirror(uint index, bool clockwise);
 	void traceBeam();
+	void drawBeamGlow();
 	void redraw();
 };
 
diff --git a/engines/nancy/ui/viewport.h b/engines/nancy/ui/viewport.h
index 7d3a8adb0e8..729c3b527fe 100644
--- a/engines/nancy/ui/viewport.h
+++ b/engines/nancy/ui/viewport.h
@@ -71,6 +71,10 @@ public:
 	uint16 getCurVerticalScroll() const { return _drawSurface.getOffsetFromOwner().y; }
 	uint16 getMaxScroll() const;
 
+	// The currently-visible scene background, in viewport-local coords. Used by
+	// puzzles that composite additively over the background (e.g. MirrorLight).
+	const Graphics::ManagedSurface &getBackground() const { return _drawSurface; }
+
 	Common::Rect convertViewportToScreen(const Common::Rect &viewportRect) const;
 	Common::Rect convertScreenToViewport(const Common::Rect &viewportRect) const;
 




More information about the Scummvm-git-logs mailing list