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

neuromancer noreply at scummvm.org
Thu Mar 26 17:17:11 UTC 2026


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

Summary:
7ecf4d98cc FREESCAPE: enable sound for eclipse cpc
5d663b7ae4 FREESCAPE: fix water indicator in eclipse cpc
f3eb3b27b6 FREESCAPE: implemented heartbeat in eclipse cpc
2d9643afca FREESCAPE: implemented heartbeat in eclipse zx
8b48455b1f FREESCAPE: completed shake off effect for shaders and tinygl
f5b782fee5 FREESCAPE: draw the UI correctly when riddles are displayed
e0091f2444 FREESCAPE: pause game while text is displayed


Commit: 7ecf4d98cc050e8efc232e3c16cc5f159b0ef1bc
    https://github.com/scummvm/scummvm/commit/7ecf4d98cc050e8efc232e3c16cc5f159b0ef1bc
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:56+01:00

Commit Message:
FREESCAPE: enable sound for eclipse cpc

Changed paths:
    engines/freescape/games/eclipse/cpc.cpp


diff --git a/engines/freescape/games/eclipse/cpc.cpp b/engines/freescape/games/eclipse/cpc.cpp
index 96460fe0a55..2c1af76c80e 100644
--- a/engines/freescape/games/eclipse/cpc.cpp
+++ b/engines/freescape/games/eclipse/cpc.cpp
@@ -107,7 +107,7 @@ void EclipseEngine::loadAssetsCPCFullGame() {
 		loadFonts(&file, 0x6076);
 		loadMessagesFixedSize(&file, 0x326, 16, 30);
 		load8bitBinary(&file, 0x626e, 16);
-		// TODO: loadSoundsCPC for full game - need to determine table offsets from TECODE.BIN
+		loadSoundsCPC(&file, 0x07C9, 104, 0x0831, 165, 0x0736, 147);
 	}
 
 	loadColorPalette();


Commit: 5d663b7ae440255f37887c677d9d5f75d4684bc4
    https://github.com/scummvm/scummvm/commit/5d663b7ae440255f37887c677d9d5f75d4684bc4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:56+01:00

Commit Message:
FREESCAPE: fix water indicator in eclipse cpc

Changed paths:
    engines/freescape/games/eclipse/cpc.cpp


diff --git a/engines/freescape/games/eclipse/cpc.cpp b/engines/freescape/games/eclipse/cpc.cpp
index 2c1af76c80e..2ae3e63fb01 100644
--- a/engines/freescape/games/eclipse/cpc.cpp
+++ b/engines/freescape/games/eclipse/cpc.cpp
@@ -212,11 +212,18 @@ void EclipseEngine::drawCPCUI(Graphics::Surface *surface) {
 	drawIndicator(surface, 45, 4, 12);
 	drawEclipseIndicator(surface, 228, 0, front, other);
 
+	int energy = _gameStateVars[k8bitVariableEnergy];
+	if (energy < 0)
+		energy = 0;
+
+	_gfx->readFromPalette(19, r, g, b);
+	uint32 blue = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+
 	Common::Rect jarBackground(124, 165, 148, 192);
 	surface->fillRect(jarBackground, back);
 
-	Common::Rect jarWater(124, 192 - _gameStateVars[k8bitVariableEnergy], 148, 192);
-	surface->fillRect(jarWater, color);
+	Common::Rect jarWater(124, 192 - energy, 148, 192);
+	surface->fillRect(jarWater, blue);
 
 	surface->fillRect(Common::Rect(225, 168, 235, 187), front);
 	drawCompass(surface, 229, 177, _yaw, 10, back);


Commit: f3eb3b27b612df74a902e6ec11549156a5892512
    https://github.com/scummvm/scummvm/commit/f3eb3b27b612df74a902e6ec11549156a5892512
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:56+01:00

Commit Message:
FREESCAPE: implemented heartbeat in eclipse cpc

Changed paths:
    engines/freescape/games/eclipse/cpc.cpp
    engines/freescape/games/eclipse/eclipse.h


diff --git a/engines/freescape/games/eclipse/cpc.cpp b/engines/freescape/games/eclipse/cpc.cpp
index 2ae3e63fb01..7f03f6d37b5 100644
--- a/engines/freescape/games/eclipse/cpc.cpp
+++ b/engines/freescape/games/eclipse/cpc.cpp
@@ -63,6 +63,69 @@ byte kCPCPaletteEclipseBorderData[4][3] = {
 
 
 
+void EclipseEngine::loadHeartFramesCPC(Common::SeekableReadStream *file, int restOffset, int beatOffset) {
+	// Decode into _eclipseSprites[0] (beat) and _eclipseSprites[1] (rest),
+	// matching the Atari ST convention for future unification.
+	// Same CLUT8→ARGB approach as Castle's loadFrameWithHeaderCPCIndexed + convertCPCSprite.
+	int offsets[2] = { beatOffset, restOffset };
+
+	byte palette[4 * 3];
+	for (int c = 0; c < 4; c++) {
+		uint8 r, g, b;
+		_gfx->selectColorFromFourColorPalette(c, r, g, b);
+		palette[c * 3 + 0] = r;
+		palette[c * 3 + 1] = g;
+		palette[c * 3 + 2] = b;
+	}
+
+	for (int f = 0; f < 2; f++) {
+		file->seek(offsets[f]);
+		int height = file->readByte();
+		int widthBytes = file->readByte();
+
+		// Decode CPC mode 1 bytes into CLUT8 indexed surface (values 0-3)
+		Graphics::ManagedSurface clut8;
+		clut8.create(widthBytes * 4, height, Graphics::PixelFormat::createFormatCLUT8());
+		for (int y = 0; y < height; y++)
+			for (int col = 0; col < widthBytes; col++) {
+				byte cpc_byte = file->readByte();
+				for (int i = 0; i < 4; i++)
+					clut8.setPixel(col * 4 + i, y, getCPCPixelMode1(cpc_byte, i));
+			}
+
+		clut8.setPalette(palette, 0, 4);
+
+		// Convert CLUT8 to target pixel format
+		Graphics::Surface *converted = _gfx->convertImageFormatIfNecessary(&clut8);
+		auto *surf = new Graphics::ManagedSurface();
+		surf->copyFrom(*converted);
+		converted->free();
+		delete converted;
+
+		_eclipseSprites.push_back(surf);
+	}
+}
+
+void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y) {
+	// CPC original: timer counts down from shield at 50Hz (_ticks rate).
+	// Beat frame shown for last 5 ticks of each cycle, rest frame for the remainder.
+	// Lower shield = faster heartbeat. At shield <= 5, heart beats constantly.
+	if (_eclipseSprites.size() < 2)
+		return;
+
+	int shield = _gameStateVars[k8bitVariableShield];
+	int beatCycle = MAX(shield, 1);
+	int phase = _ticks % beatCycle;
+	int beatStart = MAX(beatCycle - 5, 0);
+	int frame = (phase >= beatStart) ? 0 : 1;
+
+	if (phase == beatStart)
+		playSound(1, false, _soundFxHandle);
+
+	surface->copyRectToSurface(*_eclipseSprites[frame], x, y,
+		Common::Rect(_eclipseSprites[frame]->w, _eclipseSprites[frame]->h));
+}
+
 void EclipseEngine::loadAssetsCPCFullGame() {
 	Common::File file;
 
@@ -113,6 +176,9 @@ void EclipseEngine::loadAssetsCPCFullGame() {
 	loadColorPalette();
 	swapPalette(1);
 
+	if (!isEclipse2())
+		loadHeartFramesCPC(&file, 0x0CDB, 0x0D0D);
+
 	_indicators.push_back(loadBundledImage("eclipse_ankh_indicator"));
 
 	for (auto &it : _indicators)
@@ -142,6 +208,7 @@ void EclipseEngine::loadAssetsCPCDemo() {
 	loadSoundsCPC(&file, 0x0805, 104, 0x086D, 165, 0x0772, 147);
 	loadColorPalette();
 	swapPalette(1);
+	loadHeartFramesCPC(&file, 0x0D17, 0x0D49);
 
 	// This patch forces a solid color to the bottom of the chest in the area 5
 	// It was transparent in the original game
@@ -225,6 +292,8 @@ void EclipseEngine::drawCPCUI(Graphics::Surface *surface) {
 	Common::Rect jarWater(124, 192 - energy, 148, 192);
 	surface->fillRect(jarWater, blue);
 
+	drawHeartIndicator(surface, 176, 168);
+
 	surface->fillRect(Common::Rect(225, 168, 235, 187), front);
 	drawCompass(surface, 229, 177, _yaw, 10, back);
 
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index c8398c6828d..298b8d8cf68 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -97,6 +97,8 @@ public:
 	void drawScoreString(int score, int x, int y, uint32 front, uint32 back, Graphics::Surface *surface);
 
 	soundFx *load1bPCM(Common::SeekableReadStream *file, int offset);
+	void loadHeartFramesCPC(Common::SeekableReadStream *file, int restOffset, int beatOffset);
+	void drawHeartIndicator(Graphics::Surface *surface, int x, int y);
 
 	Common::Array<byte> _musicData; // TEMUSIC.ST TEXT segment (Atari ST)
 


Commit: 2d9643afcaa6120b739d413cd03759c2f65b0ef5
    https://github.com/scummvm/scummvm/commit/2d9643afcaa6120b739d413cd03759c2f65b0ef5
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:57+01:00

Commit Message:
FREESCAPE: implemented heartbeat in eclipse zx

Changed paths:
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/games/castle/castle.cpp
    engines/freescape/games/castle/castle.h
    engines/freescape/games/eclipse/cpc.cpp
    engines/freescape/games/eclipse/eclipse.cpp
    engines/freescape/games/eclipse/eclipse.h
    engines/freescape/games/eclipse/zx.cpp


diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index d97a10aa3de..49e9dafeea0 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -1290,6 +1290,29 @@ Graphics::ManagedSurface *FreescapeEngine::loadAndConvertScrImage(Common::Seekab
 	return surface;
 }
 
+Graphics::ManagedSurface *FreescapeEngine::loadFrame(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int width, int height, uint32 front) {
+	for (int i = 0; i < width * height; i++) {
+		byte color = file->readByte();
+		for (int n = 0; n < 8; n++) {
+			int y = i / width;
+			int x = (i % width) * 8 + (7 - n);
+			if ((color & (1 << n)))
+				surface->setPixel(x, y, front);
+		}
+	}
+	return surface;
+}
+
+Graphics::ManagedSurface *FreescapeEngine::loadFrameCPCIndexed(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int widthBytes, int height) {
+	for (int y = 0; y < height; y++)
+		for (int col = 0; col < widthBytes; col++) {
+			byte cpc_byte = file->readByte();
+			for (int i = 0; i < 4; i++)
+				surface->setPixel(col * 4 + i, y, getCPCPixel(cpc_byte, i, true));
+		}
+	return surface;
+}
+
 void FreescapeEngine::getTimeFromCountdown(int &seconds, int &minutes, int &hours) {
 	int countdown = _countdown;
 	int h = countdown <= 0 ? 0 : countdown / 3600;
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 61ad126f2a4..3e23d43c2e1 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -281,6 +281,8 @@ public:
 	byte *getPaletteFromNeoImage(Common::SeekableReadStream *stream, int offset);
 	Graphics::ManagedSurface *loadAndConvertNeoImage(Common::SeekableReadStream *stream, int offset, byte *palette = nullptr);
 	Graphics::ManagedSurface *loadAndConvertScrImage(Common::SeekableReadStream *stream);
+	Graphics::ManagedSurface *loadFrame(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int width, int height, uint32 front);
+	Graphics::ManagedSurface *loadFrameCPCIndexed(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int widthBytes, int height);
 	Graphics::ManagedSurface *loadAndConvertDoodleImage(Common::SeekableReadStream *bitmap, Common::SeekableReadStream *color1, Common::SeekableReadStream *color2, byte *palette);
 
 	void loadPalettes(Common::SeekableReadStream *file, int offset);
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 4ba98a56eda..0b72e67c008 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -293,18 +293,7 @@ Common::Array<Graphics::ManagedSurface *> CastleEngine::loadFramesWithHeader(Com
 	return frames;
 }
 
-Graphics::ManagedSurface *CastleEngine::loadFrame(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int width, int height, uint32 front) {
-	for (int i = 0; i < width * height; i++) {
-		byte color = file->readByte();
-		for (int n = 0; n < 8; n++) {
-			int y = i / width;
-			int x = (i % width) * 8 + (7 - n);
-			if ((color & (1 << n)))
-				surface->setPixel(x, y, front);
-		}
-	}
-	return surface;
-}
+// loadFrame moved to FreescapeEngine (freescape.cpp)
 
 Graphics::ManagedSurface *CastleEngine::loadFrameWithHeaderCPC(Common::SeekableReadStream *file, int pos, const uint32 *cpcPalette) {
 	Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
@@ -383,13 +372,7 @@ Graphics::ManagedSurface *CastleEngine::loadFrameWithHeaderCPCIndexed(Common::Se
 	Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
 	surface->create(w * 4, h, Graphics::PixelFormat::createFormatCLUT8());
 	surface->fillRect(Common::Rect(0, 0, w * 4, h), 0);
-	for (int y = 0; y < h; y++)
-		for (int col = 0; col < w; col++) {
-			byte cpc_byte = file->readByte();
-			for (int i = 0; i < 4; i++)
-				surface->setPixel(col * 4 + i, y, getCPCPixel(cpc_byte, i, true));
-		}
-	return surface;
+	return loadFrameCPCIndexed(file, surface, w, h);
 }
 
 Common::Array<Graphics::ManagedSurface *> CastleEngine::loadFramesWithHeaderCPCIndexed(Common::SeekableReadStream *file, int pos, int numFrames) {
@@ -403,12 +386,7 @@ Common::Array<Graphics::ManagedSurface *> CastleEngine::loadFramesWithHeaderCPCI
 		Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
 		surface->create(w * 4, h, Graphics::PixelFormat::createFormatCLUT8());
 		surface->fillRect(Common::Rect(0, 0, w * 4, h), 0);
-		for (int y = 0; y < h; y++)
-			for (int col = 0; col < w; col++) {
-				byte cpc_byte = file->readByte();
-				for (int i = 0; i < 4; i++)
-					surface->setPixel(col * 4 + i, y, getCPCPixel(cpc_byte, i, true));
-			}
+		loadFrameCPCIndexed(file, surface, w, h);
 		frames.push_back(surface);
 	}
 	return frames;
diff --git a/engines/freescape/games/castle/castle.h b/engines/freescape/games/castle/castle.h
index 8a0d150b367..dc19aaf8fb4 100644
--- a/engines/freescape/games/castle/castle.h
+++ b/engines/freescape/games/castle/castle.h
@@ -108,7 +108,6 @@ public:
 
 	Common::Array<Graphics::ManagedSurface *> loadFramesWithHeader(Common::SeekableReadStream *file, int pos, int numFrames, uint32 front, uint32 back);
 	Graphics::ManagedSurface *loadFrameWithHeader(Common::SeekableReadStream *file, int pos, uint32 front, uint32 back);
-	Graphics::ManagedSurface *loadFrame(Common::SeekableReadStream *file, Graphics::ManagedSurface *surface, int width, int height, uint32 back);
 
 	// CPC-specific frame loading (Mode 1: 4 pixels per byte)
 	// cpcPalette is a 4-entry array mapping CPC ink numbers (0-3) to ARGB colors
diff --git a/engines/freescape/games/eclipse/cpc.cpp b/engines/freescape/games/eclipse/cpc.cpp
index 7f03f6d37b5..628a43e10aa 100644
--- a/engines/freescape/games/eclipse/cpc.cpp
+++ b/engines/freescape/games/eclipse/cpc.cpp
@@ -66,7 +66,8 @@ byte kCPCPaletteEclipseBorderData[4][3] = {
 void EclipseEngine::loadHeartFramesCPC(Common::SeekableReadStream *file, int restOffset, int beatOffset) {
 	// Decode into _eclipseSprites[0] (beat) and _eclipseSprites[1] (rest),
 	// matching the Atari ST convention for future unification.
-	// Same CLUT8→ARGB approach as Castle's loadFrameWithHeaderCPCIndexed + convertCPCSprite.
+	// Uses loadFrameCPCIndexed (shared with Castle) for pixel decoding,
+	// then converts CLUT8→ARGB via convertImageFormatIfNecessary.
 	int offsets[2] = { beatOffset, restOffset };
 
 	byte palette[4 * 3];
@@ -83,19 +84,11 @@ void EclipseEngine::loadHeartFramesCPC(Common::SeekableReadStream *file, int res
 		int height = file->readByte();
 		int widthBytes = file->readByte();
 
-		// Decode CPC mode 1 bytes into CLUT8 indexed surface (values 0-3)
 		Graphics::ManagedSurface clut8;
 		clut8.create(widthBytes * 4, height, Graphics::PixelFormat::createFormatCLUT8());
-		for (int y = 0; y < height; y++)
-			for (int col = 0; col < widthBytes; col++) {
-				byte cpc_byte = file->readByte();
-				for (int i = 0; i < 4; i++)
-					clut8.setPixel(col * 4 + i, y, getCPCPixelMode1(cpc_byte, i));
-			}
-
+		loadFrameCPCIndexed(file, &clut8, widthBytes, height);
 		clut8.setPalette(palette, 0, 4);
 
-		// Convert CLUT8 to target pixel format
 		Graphics::Surface *converted = _gfx->convertImageFormatIfNecessary(&clut8);
 		auto *surf = new Graphics::ManagedSurface();
 		surf->copyFrom(*converted);
@@ -106,26 +99,6 @@ void EclipseEngine::loadHeartFramesCPC(Common::SeekableReadStream *file, int res
 	}
 }
 
-void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y) {
-	// CPC original: timer counts down from shield at 50Hz (_ticks rate).
-	// Beat frame shown for last 5 ticks of each cycle, rest frame for the remainder.
-	// Lower shield = faster heartbeat. At shield <= 5, heart beats constantly.
-	if (_eclipseSprites.size() < 2)
-		return;
-
-	int shield = _gameStateVars[k8bitVariableShield];
-	int beatCycle = MAX(shield, 1);
-	int phase = _ticks % beatCycle;
-	int beatStart = MAX(beatCycle - 5, 0);
-	int frame = (phase >= beatStart) ? 0 : 1;
-
-	if (phase == beatStart)
-		playSound(1, false, _soundFxHandle);
-
-	surface->copyRectToSurface(*_eclipseSprites[frame], x, y,
-		Common::Rect(_eclipseSprites[frame]->w, _eclipseSprites[frame]->h));
-}
-
 void EclipseEngine::loadAssetsCPCFullGame() {
 	Common::File file;
 
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index bf093963bbd..290b8810fa2 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -797,6 +797,27 @@ void EclipseEngine::drawIndicator(Graphics::Surface *surface, int xPosition, int
 	}
 }
 
+void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y) {
+	// Heartbeat animation shared across platforms.
+	// Timer counts down from shield at 50Hz (_ticks rate).
+	// Beat frame shown for last 5 ticks of each cycle, rest frame for the remainder.
+	// Lower shield = faster heartbeat. At shield <= 5, heart beats constantly.
+	if (_eclipseSprites.size() < 2)
+		return;
+
+	int shield = _gameStateVars[k8bitVariableShield];
+	int beatCycle = MAX(shield, 1);
+	int phase = _ticks % beatCycle;
+	int beatStart = MAX(beatCycle - 5, 0);
+	int frame = (phase >= beatStart) ? 0 : 1;
+
+	if (phase == beatStart)
+		playSound(1, false, _soundFxHandle);
+
+	surface->copyRectToSurface(*_eclipseSprites[frame], x, y,
+		Common::Rect(_eclipseSprites[frame]->w, _eclipseSprites[frame]->h));
+}
+
 void EclipseEngine::drawSensorShoot(Sensor *sensor) {
 	Math::Vector3d target;
 	float distance = 5;
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index 298b8d8cf68..00d8fa8e421 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -98,6 +98,7 @@ public:
 
 	soundFx *load1bPCM(Common::SeekableReadStream *file, int offset);
 	void loadHeartFramesCPC(Common::SeekableReadStream *file, int restOffset, int beatOffset);
+	void loadHeartFramesZX(Common::SeekableReadStream *file, int restOffset, int beatOffset);
 	void drawHeartIndicator(Graphics::Surface *surface, int x, int y);
 
 	Common::Array<byte> _musicData; // TEMUSIC.ST TEXT segment (Atari ST)
diff --git a/engines/freescape/games/eclipse/zx.cpp b/engines/freescape/games/eclipse/zx.cpp
index ae33ab9dadf..6b25aae7d68 100644
--- a/engines/freescape/games/eclipse/zx.cpp
+++ b/engines/freescape/games/eclipse/zx.cpp
@@ -52,6 +52,36 @@ void EclipseEngine::initZX() {
 	_soundIndexMissionComplete = 16;
 }
 
+void EclipseEngine::loadHeartFramesZX(Common::SeekableReadStream *file, int restOffset, int beatOffset) {
+	// ZX monochrome heart sprites with 2-byte header (height, width_bytes).
+	// Stores into _eclipseSprites[0] (beat) and [1] (rest), matching Atari convention.
+	// Uses FreescapeEngine::loadFrame for the monochrome pixel decoding.
+	//
+	// The two frames have opposite bit polarity:
+	// - BEAT: "1" bits = heart shape (ink/yellow), "0" = background (paper/red)
+	// - REST: "1" bits = background (paper/red), "0" = small heart outline (ink/yellow)
+	// So front/back colors are swapped between frames.
+	int offsets[2] = { beatOffset, restOffset };
+
+	uint32 yellow = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0xd8, 0xd8, 0x00);
+	uint32 red = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0xd8, 0x00, 0x00);
+	uint32 frontColors[2] = { red, red };
+	uint32 backColors[2] = { yellow, yellow };
+
+	for (int f = 0; f < 2; f++) {
+		file->seek(offsets[f]);
+		int height = file->readByte();
+		int widthBytes = file->readByte();
+
+		auto *surf = new Graphics::ManagedSurface();
+		surf->create(widthBytes * 8, height, _gfx->_texturePixelFormat);
+		surf->fillRect(Common::Rect(0, 0, widthBytes * 8, height), backColors[f]);
+
+		loadFrame(file, surf, widthBytes, height, frontColors[f]);
+		_eclipseSprites.push_back(surf);
+	}
+}
+
 void EclipseEngine::loadAssetsZXFullGame() {
 	Common::File file;
 
@@ -83,6 +113,7 @@ void EclipseEngine::loadAssetsZXFullGame() {
 		loadFonts(&file, 0x6163);
 		loadSpeakerFxZX(&file, 0x816, 0x86a);
 		load8bitBinary(&file, 0x635b, 4);
+		loadHeartFramesZX(&file, 0x0D62, 0x0D7C);
 
 		// These paper colors are also invalid, but to signal the use of a special effect (only in zx release)
 		_areaMap[42]->_paperColor = 0;
@@ -213,6 +244,8 @@ void EclipseEngine::drawZXUI(Graphics::Surface *surface) {
 	surface->fillRect(Common::Rect(227, 168, 235, 187), gray);
 	drawCompass(surface, 231, 177, _yaw, 10, back);
 
+	drawHeartIndicator(surface, 176, 167);
+
 	drawIndicator(surface, 65, 7, 8);
 	drawEclipseIndicator(surface, 215, 3, front, gray);
 }


Commit: 8b48455b1f845ca70009d3806585a36952190a14
    https://github.com/scummvm/scummvm/commit/8b48455b1f845ca70009d3806585a36952190a14
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:57+01:00

Commit Message:
FREESCAPE: completed shake off effect for shaders and tinygl

Changed paths:
    engines/freescape/gfx_opengl.cpp
    engines/freescape/gfx_opengl_shaders.cpp
    engines/freescape/gfx_tinygl.cpp
    engines/freescape/shaders/freescape_triangle.vertex


diff --git a/engines/freescape/gfx_opengl.cpp b/engines/freescape/gfx_opengl.cpp
index 3bf43dcd3e0..40ce2ca2186 100644
--- a/engines/freescape/gfx_opengl.cpp
+++ b/engines/freescape/gfx_opengl.cpp
@@ -276,7 +276,7 @@ void OpenGLRenderer::positionCamera(const Math::Vector3d &pos, const Math::Vecto
 	GLfloat projMatrix[16];
 	glGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
 	glLoadIdentity();
-	glTranslatef(_shakeOffset.x * 0.05f, _shakeOffset.y * 0.05f, 0.0f);
+	glTranslatef(_shakeOffset.x * 0.025f, _shakeOffset.y * 0.025f, 0.0f);
 	glMultMatrixf(projMatrix);
 	glMatrixMode(GL_MODELVIEW);
 }
diff --git a/engines/freescape/gfx_opengl_shaders.cpp b/engines/freescape/gfx_opengl_shaders.cpp
index 16976d074ec..9ed3d1464d5 100644
--- a/engines/freescape/gfx_opengl_shaders.cpp
+++ b/engines/freescape/gfx_opengl_shaders.cpp
@@ -305,7 +305,12 @@ void OpenGLShaderRenderer::positionCamera(const Math::Vector3d &pos, const Math:
 	model.transpose();
 	_mvpMatrix = proj * model;
 	_mvpMatrix.transpose();
+
+	_triangleShader->use();
+	_triangleShader->setUniform("shakeOffset",
+		Math::Vector2d(_shakeOffset.x * 0.025f, _shakeOffset.y * 0.025f));
 }
+
 void OpenGLShaderRenderer::renderSensorShoot(byte color, const Math::Vector3d sensor, const Math::Vector3d target, const Common::Rect &viewArea) {
 	glEnable(GL_BLEND);
 	glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
@@ -343,6 +348,7 @@ void OpenGLShaderRenderer::renderPlayerShootBall(byte color, const Common::Point
 	_triangleShader->use();
 	_triangleShader->setUniform("useStipple", false);
 	_triangleShader->setUniform("mvpMatrix", identity);
+	_triangleShader->setUniform("shakeOffset", Math::Vector2d(0, 0));
 
 	if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderZX) {
 		r = g = b = 255;
@@ -407,6 +413,7 @@ void OpenGLShaderRenderer::renderPlayerShootRay(byte color, const Common::Point
 	_triangleShader->use();
 	_triangleShader->setUniform("useStipple", false);
 	_triangleShader->setUniform("mvpMatrix", identity);
+	_triangleShader->setUniform("shakeOffset", Math::Vector2d(0, 0));
 
 	if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderZX) {
 		r = g = b = 255;
@@ -525,6 +532,7 @@ void OpenGLShaderRenderer::drawCelestialBody(const Math::Vector3d position, floa
 	// === Shader uniforms ===
 	_triangleShader->use();
 	_triangleShader->setUniform("mvpMatrix", billboardMVP);
+	_triangleShader->setUniform("shakeOffset", Math::Vector2d(0, 0));
 	_triangleShader->setUniform("useStipple", false);
 
 	// === Render settings ===
@@ -621,6 +629,7 @@ void OpenGLShaderRenderer::renderCrossair(const Common::Point &crossairPosition)
 	_triangleShader->use();
 	_triangleShader->setUniform("useStipple", false);
 	_triangleShader->setUniform("mvpMatrix", identity);
+	_triangleShader->setUniform("shakeOffset", Math::Vector2d(0, 0));
 
 	glEnable(GL_BLEND);
 	glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
diff --git a/engines/freescape/gfx_tinygl.cpp b/engines/freescape/gfx_tinygl.cpp
index f4b094c617f..5b2db657cfa 100644
--- a/engines/freescape/gfx_tinygl.cpp
+++ b/engines/freescape/gfx_tinygl.cpp
@@ -211,6 +211,16 @@ void TinyGLRenderer::positionCamera(const Math::Vector3d &pos, const Math::Vecto
 	tglMultMatrixf(lookMatrix.getData());
 	tglRotatef(rollAngle, 0.0f, 0.0f, 1.0f);
 	tglTranslatef(-pos.x(), -pos.y(), -pos.z());
+
+	// Apply a 2D shake effect on the projection matrix,
+	// matching the OpenGL fixed-function implementation.
+	tglMatrixMode(TGL_PROJECTION);
+	TGLfloat projMatrix[16];
+	tglGetFloatv(TGL_PROJECTION_MATRIX, projMatrix);
+	tglLoadIdentity();
+	tglTranslatef(_shakeOffset.x * 0.025f, _shakeOffset.y * 0.025f, 0.0f);
+	tglMultMatrixf(projMatrix);
+	tglMatrixMode(TGL_MODELVIEW);
 }
 
 void TinyGLRenderer::renderSensorShoot(byte color, const Math::Vector3d sensor, const Math::Vector3d player, const Common::Rect &viewArea) {
diff --git a/engines/freescape/shaders/freescape_triangle.vertex b/engines/freescape/shaders/freescape_triangle.vertex
index 6483e9a8adb..619e2720761 100644
--- a/engines/freescape/shaders/freescape_triangle.vertex
+++ b/engines/freescape/shaders/freescape_triangle.vertex
@@ -2,11 +2,13 @@ in vec3 position;
 
 uniform mat4 mvpMatrix;
 uniform vec3 color;
+uniform vec2 shakeOffset;
 
 varying vec4 var_color;
 
 void main()
 {
 	var_color = vec4(color, 1.0);
-    gl_Position = mvpMatrix * vec4(position, 1.0);
+	gl_Position = mvpMatrix * vec4(position, 1.0);
+	gl_Position.xy += shakeOffset * gl_Position.w;
 }
\ No newline at end of file


Commit: f5b782fee552425073b641f7c093bc60d2753b91
    https://github.com/scummvm/scummvm/commit/f5b782fee552425073b641f7c093bc60d2753b91
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:57+01:00

Commit Message:
FREESCAPE: draw the UI correctly when riddles are displayed

Changed paths:
    engines/freescape/freescape.h
    engines/freescape/games/castle/castle.cpp
    engines/freescape/ui.cpp


diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 3e23d43c2e1..202dceaa245 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -227,6 +227,7 @@ public:
 	void drawTitle();
 	virtual void drawBackground();
 	void clearBackground();
+	void drawPlatformUI(Graphics::Surface *surface);
 	virtual void drawUI();
 	virtual void drawInfoMenu();
 	void drawBorderScreenAndWait(Graphics::Surface *surface, int maxWait = INT_MAX);
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 0b72e67c008..bb914255e73 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -1559,10 +1559,16 @@ void CastleEngine::drawFullscreenRiddleAndWait(uint16 riddle) {
 				break;
 			}
 		}
+		updateTimeVariables();
 		_gfx->clear(0, 0, 0, true);
 		drawBorder();
-		if (_currentArea)
-			drawUI();
+		if (_currentArea) {
+			// Draw both UI and riddle on the same surface, since
+			// drawFullscreenSurface uses a single shared texture.
+			uint32 gray = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0xA0, 0xA0, 0xA0);
+			surface->fillRect(Common::Rect(0, 0, _screenW, _screenH), gray);
+			drawPlatformUI(surface);
+		}
 		drawRiddle(riddle, front, transparent, surface);
 		_gfx->flipBuffer();
 		g_system->updateScreen();
diff --git a/engines/freescape/ui.cpp b/engines/freescape/ui.cpp
index eff56e7b54a..819678d617d 100644
--- a/engines/freescape/ui.cpp
+++ b/engines/freescape/ui.cpp
@@ -368,16 +368,7 @@ void FreescapeEngine::drawFullscreenSurface(Graphics::Surface *surface) {
 	_gfx->setViewport(_viewArea);
 }
 
-void FreescapeEngine::drawUI() {
-	Graphics::Surface *surface = nullptr;
-	if (_border) { // This can be removed when all the borders are loaded
-		uint32 gray = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0xA0, 0xA0, 0xA0);
-		surface = new Graphics::Surface();
-		surface->create(_screenW, _screenH, _gfx->_texturePixelFormat);
-		surface->fillRect(_fullscreenViewArea, gray);
-	} else
-		return;
-
+void FreescapeEngine::drawPlatformUI(Graphics::Surface *surface) {
 	if (isDOS())
 		drawDOSUI(surface);
 	else if (isC64())
@@ -388,6 +379,19 @@ void FreescapeEngine::drawUI() {
 		drawCPCUI(surface);
 	else if (isAmiga() || isAtariST())
 		drawAmigaAtariSTUI(surface);
+}
+
+void FreescapeEngine::drawUI() {
+	Graphics::Surface *surface = nullptr;
+	if (_border) { // This can be removed when all the borders are loaded
+		uint32 gray = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0xA0, 0xA0, 0xA0);
+		surface = new Graphics::Surface();
+		surface->create(_screenW, _screenH, _gfx->_texturePixelFormat);
+		surface->fillRect(_fullscreenViewArea, gray);
+	} else
+		return;
+
+	drawPlatformUI(surface);
 
 	drawFullscreenSurface(surface);
 


Commit: e0091f244440bef46498393609c150888c04937a
    https://github.com/scummvm/scummvm/commit/e0091f244440bef46498393609c150888c04937a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-26T18:16:57+01:00

Commit Message:
FREESCAPE: pause game while text is displayed

Changed paths:
    engines/freescape/games/castle/castle.cpp
    engines/freescape/ui.cpp


diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index bb914255e73..4bda08d0893 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -1559,7 +1559,6 @@ void CastleEngine::drawFullscreenRiddleAndWait(uint16 riddle) {
 				break;
 			}
 		}
-		updateTimeVariables();
 		_gfx->clear(0, 0, 0, true);
 		drawBorder();
 		if (_currentArea) {
diff --git a/engines/freescape/ui.cpp b/engines/freescape/ui.cpp
index 819678d617d..7aa78105b17 100644
--- a/engines/freescape/ui.cpp
+++ b/engines/freescape/ui.cpp
@@ -302,6 +302,7 @@ void FreescapeEngine::drawFullscreenMessageAndWait(Common::String message) {
 }
 
 void FreescapeEngine::drawBorderScreenAndWait(Graphics::Surface *surface, int maxWait) {
+	PauseToken pauseToken = pauseEngine();
 	for (int i = 0; i < maxWait; i++) {
 		Common::Event event;
 		while (_eventManager->pollEvent(event)) {
@@ -347,12 +348,16 @@ void FreescapeEngine::drawBorderScreenAndWait(Graphics::Surface *surface, int ma
 
 		_gfx->clear(0, 0, 0, true);
 		drawBorder();
-		if (surface)
+		if (surface) {
+			if (_currentArea)
+				drawPlatformUI(surface);
 			drawFullscreenSurface(surface);
+		}
 		_gfx->flipBuffer();
 		g_system->updateScreen();
 		g_system->delayMillis(15); // try to target ~60 FPS
 	}
+	pauseToken.clear();
 	playSound(_soundIndexMenu, false, _soundFxHandle);
 	_gfx->clear(0, 0, 0, true);
 }




More information about the Scummvm-git-logs mailing list