[Scummvm-git-logs] scummvm master -> 42aaca95f4e3ef66184d69d3ce743ee0523ded11

neuromancer noreply at scummvm.org
Fri Mar 27 07:08: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:
845687b241 FREESCAPE: improved renderPlayerShootBall for all renders
8469bac0e5 FREESCAPE: added code to implement heartbeat in eclipse dos (ega)
81f54212c2 FREESCAPE: added missing sound table in eclipse dos (cga)
69e0ff5550 FREESCAPE: added missing indicators in driller amiga
9668a661bb FREESCAPE: load vehicle indicator indicators from game data in driller amiga
42aaca95f4 FREESCAPE: quit animation button in driller amiga


Commit: 845687b24138c45ce2cbc7bfe6b75627ad4d8dfa
    https://github.com/scummvm/scummvm/commit/845687b24138c45ce2cbc7bfe6b75627ad4d8dfa
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: improved renderPlayerShootBall for all renders

Changed paths:
    engines/freescape/gfx_opengl.cpp
    engines/freescape/gfx_opengl_shaders.cpp
    engines/freescape/gfx_tinygl.cpp
    engines/freescape/movement.cpp


diff --git a/engines/freescape/gfx_opengl.cpp b/engines/freescape/gfx_opengl.cpp
index 40ce2ca2186..7f11d3e750c 100644
--- a/engines/freescape/gfx_opengl.cpp
+++ b/engines/freescape/gfx_opengl.cpp
@@ -490,18 +490,23 @@ void OpenGLRenderer::renderPlayerShootBall(byte color, const Common::Point &posi
 	glColor4ub(r, g, b, 255);
 	int triangleAmount = 20;
 	float twicePi = (float)(2.0 * M_PI);
-	float coef = (9 - frame) / 9.0;
-	float radius = (1 - coef) * 4.0;
 
-	Common::Point initial_position(viewArea.left + viewArea.width() / 2 + 2, viewArea.height() + viewArea.top);
-	Common::Point ball_position = coef * position + (1 - coef) * initial_position;
+	// Exponential ease-out trajectory inspired by the original ZX animation.
+	// The stone shrinks as it flies into the screen (perspective).
+	float coef = 1.0f - powf(0.5f, (8 - frame + 1) / 2.0f);
+	float radius = 1.0f + frame * 0.5f;
+
+	float startX = viewArea.left + viewArea.width() / 2.0f + 2;
+	float startY = viewArea.height() + viewArea.top;
+	float ballX = coef * position.x + (1.0f - coef) * startX;
+	float ballY = coef * position.y + (1.0f - coef) * startY;
 
 	glEnableClientState(GL_VERTEX_ARRAY);
-	copyToVertexArray(0, Math::Vector3d(ball_position.x, ball_position.y, 0));
+	copyToVertexArray(0, Math::Vector3d(ballX, ballY, 0));
 
 	for (int i = 0; i <= triangleAmount; i++) {
-		float x = ball_position.x + (radius * cos(i *  twicePi / triangleAmount));
-		float y = ball_position.y + (radius * sin(i * twicePi / triangleAmount));
+		float x = ballX + (radius * cos(i * twicePi / triangleAmount));
+		float y = ballY + (radius * sin(i * twicePi / triangleAmount));
 		copyToVertexArray(i + 1, Math::Vector3d(x, y, 0));
 	}
 
diff --git a/engines/freescape/gfx_opengl_shaders.cpp b/engines/freescape/gfx_opengl_shaders.cpp
index 9ed3d1464d5..2ead02a73ea 100644
--- a/engines/freescape/gfx_opengl_shaders.cpp
+++ b/engines/freescape/gfx_opengl_shaders.cpp
@@ -375,19 +375,23 @@ void OpenGLShaderRenderer::renderPlayerShootBall(byte color, const Common::Point
 
 	int triangleAmount = 20;
 	float twicePi = (float)(2.0 * M_PI);
-	float coef = (9 - frame) / 9.0;
-	float radius = (1 - coef) * 4.0;
 
-	Common::Point position(_position.x, _screenH - _position.y);
+	// Exponential ease-out trajectory inspired by the original ZX animation.
+	float coef = 1.0f - powf(0.5f, (8 - frame + 1) / 2.0f);
+	float radius = 1.0f + frame * 0.5f;
 
-	Common::Point initial_position(viewArea.left + viewArea.width() / 2 + 2, _screenH - (viewArea.height() + viewArea.top));
-	Common::Point ball_position = coef * position + (1 - coef) * initial_position;
+	float posX = (float)_position.x;
+	float posY = (float)(_screenH - _position.y);
+	float startX = viewArea.left + viewArea.width() / 2.0f + 2;
+	float startY = (float)(_screenH - (viewArea.height() + viewArea.top));
+	float ballX = coef * posX + (1.0f - coef) * startX;
+	float ballY = coef * posY + (1.0f - coef) * startY;
 
-	copyToVertexArray(0, Math::Vector3d(remap(ball_position.x, _screenW), remap(ball_position.y, _screenH), 0));
+	copyToVertexArray(0, Math::Vector3d(remap(ballX, _screenW), remap(ballY, _screenH), 0));
 
 	for (int i = 0; i <= triangleAmount; i++) {
-		float x = remap(ball_position.x + (radius * cos(i *  twicePi / triangleAmount)), _screenW);
-		float y = remap(ball_position.y + (radius * sin(i * twicePi / triangleAmount)), _screenH);
+		float x = remap(ballX + (radius * cos(i * twicePi / triangleAmount)), _screenW);
+		float y = remap(ballY + (radius * sin(i * twicePi / triangleAmount)), _screenH);
 		copyToVertexArray(i + 1, Math::Vector3d(x, y, 0));
 	}
 
diff --git a/engines/freescape/gfx_tinygl.cpp b/engines/freescape/gfx_tinygl.cpp
index 5b2db657cfa..92bccf8df0b 100644
--- a/engines/freescape/gfx_tinygl.cpp
+++ b/engines/freescape/gfx_tinygl.cpp
@@ -271,18 +271,22 @@ void TinyGLRenderer::renderPlayerShootBall(byte color, const Common::Point &posi
 	tglColor4ub(r, g, b, 255);
 	int triangleAmount = 20;
 	float twicePi = (float)(2.0 * M_PI);
-	float coef = (9 - frame) / 9.0;
-	float radius = (1 - coef) * 4.0;
 
-	Common::Point initial_position(viewArea.left + viewArea.width() / 2 + 2, viewArea.height() + viewArea.top);
-	Common::Point ball_position = coef * position + (1 - coef) * initial_position;
+	// Exponential ease-out trajectory inspired by the original ZX animation.
+	float coef = 1.0f - powf(0.5f, (8 - frame + 1) / 2.0f);
+	float radius = 1.0f + frame * 0.5f;
+
+	float startX = viewArea.left + viewArea.width() / 2.0f + 2;
+	float startY = viewArea.height() + viewArea.top;
+	float ballX = coef * position.x + (1.0f - coef) * startX;
+	float ballY = coef * position.y + (1.0f - coef) * startY;
 
 	tglEnableClientState(TGL_VERTEX_ARRAY);
-	copyToVertexArray(0, Math::Vector3d(ball_position.x, ball_position.y, 0));
+	copyToVertexArray(0, Math::Vector3d(ballX, ballY, 0));
 
-	for(int i = 0; i <= triangleAmount; i++) {
-		float x = ball_position.x + (radius * cos(i *  twicePi / triangleAmount));
-		float y = ball_position.y + (radius * sin(i * twicePi / triangleAmount));
+	for (int i = 0; i <= triangleAmount; i++) {
+		float x = ballX + (radius * cos(i * twicePi / triangleAmount));
+		float y = ballY + (radius * sin(i * twicePi / triangleAmount));
 		copyToVertexArray(i + 1, Math::Vector3d(x, y, 0));
 	}
 
diff --git a/engines/freescape/movement.cpp b/engines/freescape/movement.cpp
index f61677c5e6c..3266cb63529 100644
--- a/engines/freescape/movement.cpp
+++ b/engines/freescape/movement.cpp
@@ -236,7 +236,7 @@ void FreescapeEngine::shoot() {
 
 	playSound(_soundIndexShoot, false, _movementSoundHandle);
 	g_system->delayMillis(2);
-	_shootingFrames = 10;
+	_shootingFrames = 8;
 
 	// Convert to normalized coordinates [-1, 1]
 	float ndcX = (2.0f * (_crossairPosition.x - _viewArea.left) / _viewArea.width()) - 1.0f;


Commit: 8469bac0e5296bdea8e12d305e662a1ca707399b
    https://github.com/scummvm/scummvm/commit/8469bac0e5296bdea8e12d305e662a1ca707399b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: added code to implement heartbeat in eclipse dos (ega)

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


diff --git a/engines/freescape/games/eclipse/dos.cpp b/engines/freescape/games/eclipse/dos.cpp
index 032e7706ada..dbb887e9e3d 100644
--- a/engines/freescape/games/eclipse/dos.cpp
+++ b/engines/freescape/games/eclipse/dos.cpp
@@ -39,6 +39,71 @@ void EclipseEngine::initDOS() {
 	_soundIndexAreaChange = 5;
 }
 
+void EclipseEngine::loadHeartFramesDOS(Common::SeekableReadStream *file, int restOffset, int beatOffset) {
+	// Stores into _eclipseSprites[0] (beat) and [1] (rest).
+	int offsets[2] = { beatOffset, restOffset };
+
+	for (int f = 0; f < 2; f++) {
+		file->seek(offsets[f]);
+		int height = file->readByte();
+		int widthBytes = file->readByte();
+
+		if (_renderMode == Common::kRenderEGA) {
+			// EGA: 4 plane pointers followed by monochrome data per plane.
+			int planeFileOffsets[4];
+			for (int p = 0; p < 4; p++)
+				planeFileOffsets[p] = file->readUint16LE() + 0x200;
+
+			Graphics::ManagedSurface clut8;
+			clut8.create(widthBytes * 8, height, Graphics::PixelFormat::createFormatCLUT8());
+			clut8.fillRect(Common::Rect(0, 0, widthBytes * 8, height), 0);
+
+			for (int p = 0; p < 4; p++) {
+				Graphics::ManagedSurface plane;
+				plane.create(widthBytes * 8, height, Graphics::PixelFormat::createFormatCLUT8());
+				plane.fillRect(Common::Rect(0, 0, widthBytes * 8, height), 0);
+
+				file->seek(planeFileOffsets[p]);
+				loadFrame(file, &plane, widthBytes, height, 1);
+
+				for (int y = 0; y < height; y++)
+					for (int x = 0; x < widthBytes * 8; x++)
+						if (plane.getPixel(x, y))
+							clut8.setPixel(x, y, clut8.getPixel(x, y) | (1 << p));
+			}
+
+			clut8.setPalette((byte *)kEGADefaultPalette, 0, 16);
+
+			Graphics::Surface *converted = _gfx->convertImageFormatIfNecessary(&clut8);
+			auto *surf = new Graphics::ManagedSurface();
+			surf->copyFrom(*converted);
+			converted->free();
+			delete converted;
+			_eclipseSprites.push_back(surf);
+		} else {
+			// CGA: packed 2-bit pixels (4 pixels per byte), no planes.
+			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 b = file->readByte();
+					for (int px = 0; px < 4; px++)
+						clut8.setPixel(col * 4 + px, y, (b >> (6 - px * 2)) & 3);
+				}
+
+			clut8.setPalette((byte *)kCGAPaletteRedGreenBright, 0, 4);
+
+			Graphics::Surface *converted = _gfx->convertImageFormatIfNecessary(&clut8);
+			auto *surf = new Graphics::ManagedSurface();
+			surf->copyFrom(*converted);
+			converted->free();
+			delete converted;
+			_eclipseSprites.push_back(surf);
+		}
+	}
+}
+
 void EclipseEngine::loadAssetsDOSFullGame() {
 	Common::File file;
 	if (_renderMode == Common::kRenderEGA) {
@@ -62,6 +127,8 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 		_border = load8bitBinImage(&file, 0x210);
 		_border->setPalette((byte *)&kEGADefaultPalette, 0, 16);
 
+		loadHeartFramesDOS(&file, 0x76AB, 0x76FD);
+
 		_indicators.push_back(loadBundledImage("eclipse_ankh_indicator"));
 
 		for (auto &it : _indicators)
@@ -85,6 +152,8 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 		load8bitBinary(&file, 0x2530, 4);
 		_border = load8bitBinImage(&file, 0x210);
 		_border->setPalette((byte *)&kCGAPaletteRedGreen, 0, 4);
+		// TODO: CGA heart palette changes per area, needs re-decoding on area change
+		// loadHeartFramesDOS(&file, 0x5F52, 0x5F84);
 		swapPalette(_startArea);
 	} else
 		error("Invalid or unsupported render mode %s for Total Eclipse", Common::getRenderModeDescription(_renderMode));
@@ -161,6 +230,7 @@ void EclipseEngine::drawDOSUI(Graphics::Surface *surface) {
 	Common::Rect jarWater(124, 192 - _gameStateVars[k8bitVariableEnergy], 148, 192);
 
 	drawIndicator(surface, 41, 4, 16);
+	drawHeartIndicator(surface, 176, 168);
 	if (_renderMode == Common::kRenderEGA) {
 		surface->fillRect(jarWater, blue);
 		drawEclipseIndicator(surface, 228, 0, color3, color1);
diff --git a/engines/freescape/games/eclipse/eclipse.h b/engines/freescape/games/eclipse/eclipse.h
index 00d8fa8e421..6093b760ef4 100644
--- a/engines/freescape/games/eclipse/eclipse.h
+++ b/engines/freescape/games/eclipse/eclipse.h
@@ -99,6 +99,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 loadHeartFramesDOS(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: 81f54212c21ea1336f2b3005f7edb207293911f2
    https://github.com/scummvm/scummvm/commit/81f54212c21ea1336f2b3005f7edb207293911f2
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: added missing sound table in eclipse dos (cga)

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


diff --git a/engines/freescape/games/eclipse/dos.cpp b/engines/freescape/games/eclipse/dos.cpp
index dbb887e9e3d..76ec01a6b5e 100644
--- a/engines/freescape/games/eclipse/dos.cpp
+++ b/engines/freescape/games/eclipse/dos.cpp
@@ -148,6 +148,7 @@ void EclipseEngine::loadAssetsDOSFullGame() {
 
 		loadMessagesFixedSize(&file, 0x594f, 16, 20);
 		loadSoundsFx(&file, 0xb9f0, 5);
+		loadSpeakerFxDOS(&file, 0x5BD6 + 0x200, 0x5AE1 + 0x200, 20);
 		loadFonts(&file, 0xb785);
 		load8bitBinary(&file, 0x2530, 4);
 		_border = load8bitBinImage(&file, 0x210);


Commit: 69e0ff555072b4962840fbb3fed2c680d3f5bd27
    https://github.com/scummvm/scummvm/commit/69e0ff555072b4962840fbb3fed2c680d3f5bd27
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: added missing indicators in driller amiga

Changed paths:
    engines/freescape/games/driller/amiga.cpp
    engines/freescape/games/driller/driller.h


diff --git a/engines/freescape/games/driller/amiga.cpp b/engines/freescape/games/driller/amiga.cpp
index 15d1a6d27de..ef1ae1aeef3 100644
--- a/engines/freescape/games/driller/amiga.cpp
+++ b/engines/freescape/games/driller/amiga.cpp
@@ -26,6 +26,146 @@
 
 namespace Freescape {
 
+static void decodeAmigaSprite(Common::SeekableReadStream *file, Graphics::ManagedSurface *surf,
+		int dataOffset, int widthWords, int height, byte *palette,
+		const Graphics::PixelFormat &fmt) {
+	for (int y = 0; y < height; y++) {
+		for (int col = 0; col < widthWords; col++) {
+			int off = dataOffset + (y * widthWords + col) * 8;
+			file->seek(off);
+			uint16 p0 = file->readUint16BE();
+			uint16 p1 = file->readUint16BE();
+			uint16 p2 = file->readUint16BE();
+			uint16 p3 = file->readUint16BE();
+			for (int bit = 0; bit < 16; bit++) {
+				byte colorIdx = 0;
+				if (p0 & (0x8000 >> bit)) colorIdx |= 1;
+				if (p1 & (0x8000 >> bit)) colorIdx |= 2;
+				if (p2 & (0x8000 >> bit)) colorIdx |= 4;
+				if (p3 & (0x8000 >> bit)) colorIdx |= 8;
+				if (colorIdx == 0)
+					continue;
+				uint32 color = fmt.ARGBToColor(0xFF,
+					palette[colorIdx * 3], palette[colorIdx * 3 + 1], palette[colorIdx * 3 + 2]);
+				surf->setPixel(col * 16 + bit, y, color);
+			}
+		}
+	}
+}
+
+void DrillerEngine::loadRigSprites(Common::SeekableReadStream *file, int sprigsOffset) {
+	// SPRIGS: 2 word columns × 25 rows × 5 frames, stride=$1A0 (416 bytes)
+	const int frameStride = 0x1A0;
+	const int numFrames = 5;
+	uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0, 0, 0);
+
+	// Get the console palette
+	byte *palette = nullptr;
+	if (_variant & GF_AMIGA_RETAIL)
+		palette = getPaletteFromNeoImage(file, 0x137f4);
+	else {
+		Common::File neoFile;
+		neoFile.open("console.neo");
+		if (neoFile.isOpen())
+			palette = getPaletteFromNeoImage(&neoFile, 0);
+	}
+	if (!palette)
+		return;
+
+	for (int f = 0; f < numFrames; f++) {
+		auto *surf = new Graphics::ManagedSurface();
+		surf->create(32, 25, _gfx->_texturePixelFormat);
+		surf->fillRect(Common::Rect(0, 0, 32, 25), transparent);
+		decodeAmigaSprite(file, surf, sprigsOffset + (f + 1) * frameStride, 2, 25, palette, _gfx->_texturePixelFormat);
+		_rigSprites.push_back(surf);
+	}
+
+	free(palette);
+}
+
+void DrillerEngine::loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
+		int stepOffset, int angleOffset) {
+	uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0, 0, 0);
+
+	// Step indicator: 1 word × 4 rows, 8 frames, stride=40
+	for (int f = 0; f < 8; f++) {
+		auto *surf = new Graphics::ManagedSurface();
+		surf->create(16, 4, _gfx->_texturePixelFormat);
+		surf->fillRect(Common::Rect(0, 0, 16, 4), transparent);
+		decodeAmigaSprite(file, surf, stepOffset + f * 40, 1, 4, palette, _gfx->_texturePixelFormat);
+		_stepSprites.push_back(surf);
+	}
+
+	// Angle indicator: 1 word × 4 rows, 8 frames, stride=40
+	for (int f = 0; f < 8; f++) {
+		auto *surf = new Graphics::ManagedSurface();
+		surf->create(16, 4, _gfx->_texturePixelFormat);
+		surf->fillRect(Common::Rect(0, 0, 16, 4), transparent);
+		decodeAmigaSprite(file, surf, angleOffset + f * 40, 1, 4, palette, _gfx->_texturePixelFormat);
+		_angleSprites.push_back(surf);
+	}
+}
+
+void DrillerEngine::loadCompassStrips(Common::SeekableReadStream *file, byte *palette,
+		int pitchStripOffset, int yawCogOffset) {
+	uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0, 0, 0);
+
+	// Pitch strip (SPRATT): 32px wide, 144+29=173 rows of continuous data.
+	// stride=16 bytes per row, 2 word columns × 4 planes = 16 bytes/row.
+	// 144 start positions, 29 visible rows at a time → need 173 total rows.
+	{
+		int totalRows = 144 + 29;
+		_compassPitchStrip = new Graphics::ManagedSurface();
+		_compassPitchStrip->create(32, totalRows, _gfx->_texturePixelFormat);
+		_compassPitchStrip->fillRect(Common::Rect(0, 0, 32, totalRows), black);
+		decodeAmigaSprite(file, _compassPitchStrip, pitchStripOffset, 2, totalRows, palette, _gfx->_texturePixelFormat);
+	}
+
+	// Yaw compass (SPRCOG): pre-render all 72 rotation frames.
+	// The original uses 70 bytes of pre-computed needle data, accessed at different
+	// byte offsets and bit-shifted to produce 72 unique 30×5 pixel frames.
+	// Each frame: read long(4)+word(2) per row, shift left, mask with $3FFFFE00.
+	// The needle is written to bitplane 1 only (green in Amiga palette).
+	{
+		byte cogData[70];
+		file->seek(yawCogOffset);
+		file->read(cogData, 70);
+
+		uint32 needleColor = _gfx->_texturePixelFormat.ARGBToColor(0xFF,
+			palette[2 * 3], palette[2 * 3 + 1], palette[2 * 3 + 2]); // bitplane 1 = color 2
+
+		uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0, 0, 0);
+		for (int rot = 0; rot < 72; rot++) {
+			auto *surf = new Graphics::ManagedSurface();
+			surf->create(30, 5, _gfx->_texturePixelFormat);
+			surf->fillRect(Common::Rect(0, 0, 30, 5), transparent);
+
+			int wordOff = (rot >> 3) & ~1;
+			int bitShift = rot & 15;
+			int a1 = wordOff;
+
+			for (int row = 0; row < 5; row++) {
+				uint32 longVal = ((uint32)cogData[a1] << 24) | ((uint32)cogData[a1+1] << 16) |
+				                 ((uint32)cogData[a1+2] << 8) | cogData[a1+3];
+				uint16 wordVal = ((uint16)cogData[a1+4] << 8) | cogData[a1+5];
+
+				longVal = (longVal << bitShift);
+				uint32 wordExt = ((uint32)wordVal << bitShift) >> 16;
+				uint32 result = (longVal | wordExt) & 0x3FFFFE00;
+
+				for (int b = 0; b < 30; b++) {
+					if (result & (0x40000000 >> b))
+						surf->setPixel(b, row, needleColor);
+				}
+
+				a1 += 14; // 6 bytes data + 8 bytes skip per row
+			}
+
+			_compassYawFrames.push_back(surf);
+		}
+	}
+}
+
 void DrillerEngine::loadAssetsAmigaFullGame() {
 	Common::File file;
 	if (_variant & GF_AMIGA_RETAIL) {
@@ -48,6 +188,12 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 		load8bitBinary(&file, 0x29c16, 16);
 		loadPalettes(&file, 0x297d4);
 		loadSoundsFx(&file, 0x30e80, 25);
+
+		byte *palette = getPaletteFromNeoImage(&file, 0x137f4);
+		loadRigSprites(&file, 0x2407A);
+		loadIndicatorSprites(&file, palette, 0x26F9A, 0x27222);
+		loadCompassStrips(&file, palette, 0x23316, 0x26F4C);
+		free(palette);
 	} else if (_variant & GF_AMIGA_BUDGET) {
 		file.open("lift.neo");
 		if (!file.isOpen())
@@ -78,6 +224,18 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 		load8bitBinary(&file, 0x21a3e, 16);
 		loadPalettes(&file, 0x215fc);
 
+		byte *palette = nullptr;
+		Common::File neoFile;
+		neoFile.open("console.neo");
+		if (neoFile.isOpen())
+			palette = getPaletteFromNeoImage(&neoFile, 0);
+		loadRigSprites(&file, 0x1B8C8);
+		if (palette) {
+			loadIndicatorSprites(&file, palette, 0x1E288, 0x1E510);
+			loadCompassStrips(&file, palette, 0x1AB64, 0x1E23A);
+		}
+		free(palette);
+
 		file.close();
 		file.open("soundfx");
 		if (!file.isOpen())
@@ -290,6 +448,48 @@ void DrillerEngine::drawAmigaAtariSTUI(Graphics::Surface *surface) {
 		else
 			surface->copyRectToSurface(*_indicators[_playerHeightNumber], 106, 128, Common::Rect(_indicators[1]->w, _indicators[1]->h));
 	}
+
+	// Step indicator: shows current step size (0-7)
+	if (!_stepSprites.empty()) {
+		int frame = _playerStepIndex % _stepSprites.size();
+		surface->copyRectToSurfaceWithKey(*_stepSprites[frame], 48, 160,
+			Common::Rect(_stepSprites[frame]->w, _stepSprites[frame]->h), transparent);
+	}
+
+	// Angle/compass indicator: shows current rotation angle setting (0-7)
+	if (!_angleSprites.empty()) {
+		int frame = _angleRotationIndex % _angleSprites.size();
+		surface->copyRectToSurfaceWithKey(*_angleSprites[frame], 64, 160,
+			Common::Rect(_angleSprites[frame]->w, _angleSprites[frame]->h), transparent);
+	}
+
+	// Pitch compass (SPRATT): vertically scrolling strip at x=$4E=78, y=$89=137
+	// Mask $FFFC,$0078 means only 14 pixels visible: 2 from column 0 right + 12 from column 1
+	// Visible pixel range: x=14 to x=27 within the 32px strip (bits 0-1 of col0 + bits 0-2,7-15 of col1)
+	if (_compassPitchStrip) {
+		int pos = ((int)(_pitch * 0.4f) + 144) % 144;
+		Common::Rect srcRect(14, pos, 28, pos + 29);
+		surface->copyRectToSurface(*_compassPitchStrip, 78, 138, srcRect);
+	}
+
+	// Yaw compass: purple gradient background (SPRCBG) drawn first,
+	// then scrolling N/E/S/W needle (SPRCOG) drawn on top one line below.
+	// Background at x=$32→48, y=$8E=142. Needle at y=$8E+1=143.
+	if (!_compassYawFrames.empty()) {
+		float yaw = _yaw;
+		if (yaw < 0) yaw += 360;
+		if (yaw >= 360) yaw -= 360;
+		int rot = ((int)(yaw / 5.0f)) % 72;
+		surface->copyRectToSurfaceWithKey(*_compassYawFrames[rot], 49, 143,
+			Common::Rect(_compassYawFrames[rot]->w, _compassYawFrames[rot]->h), transparent);
+	}
+
+	// Drilling rig animation: cycles through 5 frames when rig is placed
+	if (!_rigSprites.empty() && _drillStatusByArea[_currentArea->getAreaID()] == 1) {
+		int frame = (_ticks / 7) % _rigSprites.size();
+		surface->copyRectToSurfaceWithKey(*_rigSprites[frame], 272, 143,
+			Common::Rect(_rigSprites[frame]->w, _rigSprites[frame]->h), transparent);
+	}
 }
 
 void DrillerEngine::drawString(const DrillerFontSize size, const Common::String &str, int x, int y, uint32 primaryColor, uint32 secondaryColor, uint32 backColor, Graphics::Surface *surface) {
@@ -329,6 +529,7 @@ void DrillerEngine::initAmigaAtari() {
 	_loadGameArea = Common::Rect(9, 156, 39, 164);
 
 	_borderExtra = nullptr;
+	_compassPitchStrip = nullptr;
 	_borderExtraTexture = nullptr;
 
 	_soundIndexShoot = 1;
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index 52ea8da286d..47d53e961d0 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -105,6 +105,20 @@ private:
 	void initAmigaAtari();
 	void initDOS();
 	void initZX();
+
+	// Amiga/Atari UI sprite indicators loaded from executable
+	Common::Array<Graphics::ManagedSurface *> _rigSprites;     // 5 rig animation frames
+	Common::Array<Graphics::ManagedSurface *> _stepSprites;    // 8 step indicator frames
+	Common::Array<Graphics::ManagedSurface *> _angleSprites;   // 8 angle/compass frames
+	void loadRigSprites(Common::SeekableReadStream *file, int sprigsOffset);
+	void loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
+		int stepOffset, int angleOffset);
+
+	// Compass indicators loaded from executable
+	Graphics::ManagedSurface *_compassPitchStrip;  // pitch: 32px wide × (144+29) rows scrolling strip
+	Common::Array<Graphics::ManagedSurface *> _compassYawFrames; // yaw: 72 pre-rendered 30×5 frames
+	void loadCompassStrips(Common::SeekableReadStream *file, byte *palette,
+		int pitchStripOffset, int yawCogOffset);
 	void initCPC();
 	void initC64();
 


Commit: 9668a661bbda4fc9b0f9fed4d5492e37b25a78f0
    https://github.com/scummvm/scummvm/commit/9668a661bbda4fc9b0f9fed4d5492e37b25a78f0
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: load vehicle indicator indicators from game data in driller amiga

Changed paths:
    engines/freescape/games/driller/amiga.cpp
    engines/freescape/games/driller/driller.h


diff --git a/engines/freescape/games/driller/amiga.cpp b/engines/freescape/games/driller/amiga.cpp
index ef1ae1aeef3..b2735436944 100644
--- a/engines/freescape/games/driller/amiga.cpp
+++ b/engines/freescape/games/driller/amiga.cpp
@@ -84,7 +84,7 @@ void DrillerEngine::loadRigSprites(Common::SeekableReadStream *file, int sprigsO
 }
 
 void DrillerEngine::loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
-		int stepOffset, int angleOffset) {
+		int stepOffset, int angleOffset, int vehicleOffset) {
 	uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0, 0, 0);
 
 	// Step indicator: 1 word × 4 rows, 8 frames, stride=40
@@ -104,6 +104,19 @@ void DrillerEngine::loadIndicatorSprites(Common::SeekableReadStream *file, byte
 		decodeAmigaSprite(file, surf, angleOffset + f * 40, 1, 4, palette, _gfx->_texturePixelFormat);
 		_angleSprites.push_back(surf);
 	}
+
+	// Vehicle indicator: 4 words × 43 rows, 5 frames, stride=1408 ($580)
+	// Frame 0=fly, frames 1-4=tank heights 0-3
+	{
+		uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0, 0, 0);
+		for (int f = 0; f < 5; f++) {
+			auto *surf = new Graphics::ManagedSurface();
+			surf->create(64, 43, _gfx->_texturePixelFormat);
+			surf->fillRect(Common::Rect(0, 0, 64, 43), black);
+			decodeAmigaSprite(file, surf, vehicleOffset + f * 0x580, 4, 43, palette, _gfx->_texturePixelFormat);
+			_vehicleSprites.push_back(surf);
+		}
+	}
 }
 
 void DrillerEngine::loadCompassStrips(Common::SeekableReadStream *file, byte *palette,
@@ -191,7 +204,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 
 		byte *palette = getPaletteFromNeoImage(&file, 0x137f4);
 		loadRigSprites(&file, 0x2407A);
-		loadIndicatorSprites(&file, palette, 0x26F9A, 0x27222);
+		loadIndicatorSprites(&file, palette, 0x26F9A, 0x27222, 0x24D88);
 		loadCompassStrips(&file, palette, 0x23316, 0x26F4C);
 		free(palette);
 	} else if (_variant & GF_AMIGA_BUDGET) {
@@ -231,7 +244,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 			palette = getPaletteFromNeoImage(&neoFile, 0);
 		loadRigSprites(&file, 0x1B8C8);
 		if (palette) {
-			loadIndicatorSprites(&file, palette, 0x1E288, 0x1E510);
+			loadIndicatorSprites(&file, palette, 0x1E288, 0x1E510, 0x1C5D6);
 			loadCompassStrips(&file, palette, 0x1AB64, 0x1E23A);
 		}
 		free(palette);
@@ -442,7 +455,15 @@ void DrillerEngine::drawAmigaAtariSTUI(Graphics::Surface *surface) {
 		surface->fillRect(energyBar, yellow);
 	}
 
-	if (_indicators.size() > 0) {
+	if (!_vehicleSprites.empty()) {
+		int frame = _flyMode ? 0 : (_playerHeightNumber + 1);
+		frame = CLIP(frame, 0, (int)_vehicleSprites.size() - 1);
+		// Mask $FF00,$0000,$0000,$0007: 8 bits transparent left, 3 bits transparent right
+		// Visible pixels: x=8 to x=60 within the 64px sprite
+		surface->copyRectToSurface(*_vehicleSprites[frame], 104, 126,
+			Common::Rect(8, 0, 61, _vehicleSprites[frame]->h));
+	} else if (_indicators.size() > 0) {
+		// Fallback to bundled images
 		if (_flyMode)
 			surface->copyRectToSurface(*_indicators[4], 106, 128, Common::Rect(_indicators[1]->w, _indicators[1]->h));
 		else
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index 47d53e961d0..b1895ccb2f6 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -110,9 +110,10 @@ private:
 	Common::Array<Graphics::ManagedSurface *> _rigSprites;     // 5 rig animation frames
 	Common::Array<Graphics::ManagedSurface *> _stepSprites;    // 8 step indicator frames
 	Common::Array<Graphics::ManagedSurface *> _angleSprites;   // 8 angle/compass frames
+	Common::Array<Graphics::ManagedSurface *> _vehicleSprites; // 5 vehicle mode frames (fly + 4 tank heights)
 	void loadRigSprites(Common::SeekableReadStream *file, int sprigsOffset);
 	void loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
-		int stepOffset, int angleOffset);
+		int stepOffset, int angleOffset, int vehicleOffset);
 
 	// Compass indicators loaded from executable
 	Graphics::ManagedSurface *_compassPitchStrip;  // pitch: 32px wide × (144+29) rows scrolling strip


Commit: 42aaca95f4e3ef66184d69d3ce743ee0523ded11
    https://github.com/scummvm/scummvm/commit/42aaca95f4e3ef66184d69d3ce743ee0523ded11
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-27T08:08:04+01:00

Commit Message:
FREESCAPE: quit animation button in driller amiga

Changed paths:
    engines/freescape/games/driller/amiga.cpp
    engines/freescape/games/driller/driller.cpp
    engines/freescape/games/driller/driller.h


diff --git a/engines/freescape/games/driller/amiga.cpp b/engines/freescape/games/driller/amiga.cpp
index b2735436944..4b2615f87d8 100644
--- a/engines/freescape/games/driller/amiga.cpp
+++ b/engines/freescape/games/driller/amiga.cpp
@@ -84,7 +84,7 @@ void DrillerEngine::loadRigSprites(Common::SeekableReadStream *file, int sprigsO
 }
 
 void DrillerEngine::loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
-		int stepOffset, int angleOffset, int vehicleOffset) {
+		int stepOffset, int angleOffset, int vehicleOffset, int quitOffset) {
 	uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0, 0, 0);
 
 	// Step indicator: 1 word × 4 rows, 8 frames, stride=40
@@ -117,6 +117,19 @@ void DrillerEngine::loadIndicatorSprites(Common::SeekableReadStream *file, byte
 			_vehicleSprites.push_back(surf);
 		}
 	}
+
+	// Quit/abort indicator: 2 words × 8 rows, 11 frames, stride=$90=144
+	// Frames 0-6: shutter animation, 7-10: confirmation squares filling in
+	{
+		uint32 black = _gfx->_texturePixelFormat.ARGBToColor(0xFF, 0, 0, 0);
+		for (int f = 0; f < 11; f++) {
+			auto *surf = new Graphics::ManagedSurface();
+			surf->create(32, 8, _gfx->_texturePixelFormat);
+			surf->fillRect(Common::Rect(0, 0, 32, 8), black);
+			decodeAmigaSprite(file, surf, quitOffset + f * 0x90, 2, 8, palette, _gfx->_texturePixelFormat);
+			_quitSprites.push_back(surf);
+		}
+	}
 }
 
 void DrillerEngine::loadCompassStrips(Common::SeekableReadStream *file, byte *palette,
@@ -204,7 +217,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 
 		byte *palette = getPaletteFromNeoImage(&file, 0x137f4);
 		loadRigSprites(&file, 0x2407A);
-		loadIndicatorSprites(&file, palette, 0x26F9A, 0x27222, 0x24D88);
+		loadIndicatorSprites(&file, palette, 0x26F9A, 0x27222, 0x24D88, 0x26912);
 		loadCompassStrips(&file, palette, 0x23316, 0x26F4C);
 		free(palette);
 	} else if (_variant & GF_AMIGA_BUDGET) {
@@ -244,7 +257,7 @@ void DrillerEngine::loadAssetsAmigaFullGame() {
 			palette = getPaletteFromNeoImage(&neoFile, 0);
 		loadRigSprites(&file, 0x1B8C8);
 		if (palette) {
-			loadIndicatorSprites(&file, palette, 0x1E288, 0x1E510, 0x1C5D6);
+			loadIndicatorSprites(&file, palette, 0x1E288, 0x1E510, 0x1C5D6, 0x1DC00);
 			loadCompassStrips(&file, palette, 0x1AB64, 0x1E23A);
 		}
 		free(palette);
@@ -505,6 +518,30 @@ void DrillerEngine::drawAmigaAtariSTUI(Graphics::Surface *surface) {
 			Common::Rect(_compassYawFrames[rot]->w, _compassYawFrames[rot]->h), transparent);
 	}
 
+	// Quit indicator (ABORTSQ): shows on the console when quit is initiated.
+	// First click: shutter rolls down (frames 0-6), then shows 3 empty squares (frame 7).
+	// Clicks 2-4: squares fill in (frames 8-10). Fourth click = quit confirmed.
+	// Mask $0000,$0FFF: 20 visible pixels.
+	// Quit sequence from assembly (ABORTSQ):
+	// Click 1: shutter rolls down (frames 0-6), settles on frame 7 (3 empty lights)
+	// Click 2: frame 8 (first light on)
+	// Click 3: frame 9 (second light on)
+	// Click 4: frame 10 (third light on, bar turns green) → next click quits
+	if (!_quitSprites.empty() && _quitConfirmCounter > 0) {
+		int frame;
+		if (_quitConfirmCounter == 1) {
+			// Shutter intro: animate frames 0-6, then hold frame 7
+			int shutterFrame = (_ticks - _quitStartTicks) / 2;
+			frame = (shutterFrame >= 7) ? 7 : shutterFrame;
+		} else {
+			// Counter 2→frame 8, 3→frame 9, 4→frame 10
+			frame = 6 + _quitConfirmCounter;
+		}
+		frame = CLIP(frame, 0, (int)_quitSprites.size() - 1);
+		surface->copyRectToSurface(*_quitSprites[frame], 176, 5,
+			Common::Rect(0, 0, 20, _quitSprites[frame]->h));
+	}
+
 	// Drilling rig animation: cycles through 5 frames when rig is placed
 	if (!_rigSprites.empty() && _drillStatusByArea[_currentArea->getAreaID()] == 1) {
 		int frame = (_ticks / 7) % _rigSprites.size();
@@ -551,6 +588,9 @@ void DrillerEngine::initAmigaAtari() {
 
 	_borderExtra = nullptr;
 	_compassPitchStrip = nullptr;
+	_quitConfirmCounter = 0;
+	_quitStartTicks = 0;
+	_quitArea = Common::Rect(188, 5, 208, 13);
 	_borderExtraTexture = nullptr;
 
 	_soundIndexShoot = 1;
diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index 0075625553c..e1894280cfa 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -509,6 +509,11 @@ Math::Vector3d getProjectionToPlane(const Math::Vector3d &vect, const Math::Vect
 }
 
 void DrillerEngine::pressedKey(const int keycode) {
+	// Any key press during quit confirmation cancels it
+	if ((isAmiga() || isAtariST()) && _quitConfirmCounter > 0) {
+		_quitConfirmCounter = 0;
+	}
+
 	if (keycode == kActionIncreaseStepSize) {
 		increaseStepSize();
 	} else if (keycode == kActionDecreaseStepSize) {
@@ -869,6 +874,8 @@ void DrillerEngine::removeDrill(Area *area) {
 
 void DrillerEngine::initGameState() {
 	FreescapeEngine::initGameState();
+	_quitConfirmCounter = 0;
+	_quitStartTicks = 0;
 
 	for (auto &it : _areaMap) {
 		if (_drillStatusByArea[it._key] != kDrillerNoRig)
@@ -960,6 +967,14 @@ bool DrillerEngine::onScreenControls(Common::Point mouse) {
 		loadGameDialog();
 		_gfx->setViewport(_viewArea);
 		return true;
+	} else if ((isAmiga() || isAtariST()) && !_quitSprites.empty() && _quitArea.contains(mouse)) {
+		if (_quitConfirmCounter == 0)
+			_quitStartTicks = _ticks;
+		_quitConfirmCounter++;
+		if (_quitConfirmCounter > 4) {
+			_gameStateControl = kFreescapeGameStateEnd;
+		}
+		return true;
 	}
 	return false;
 }
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index b1895ccb2f6..fcbd20e71cc 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -111,9 +111,13 @@ private:
 	Common::Array<Graphics::ManagedSurface *> _stepSprites;    // 8 step indicator frames
 	Common::Array<Graphics::ManagedSurface *> _angleSprites;   // 8 angle/compass frames
 	Common::Array<Graphics::ManagedSurface *> _vehicleSprites; // 5 vehicle mode frames (fly + 4 tank heights)
+	Common::Array<Graphics::ManagedSurface *> _quitSprites;   // 11 quit animation frames
+	int _quitConfirmCounter;  // 0=not quitting, 1-4=waiting for confirmations
+	int _quitStartTicks;      // _ticks when quit was initiated (for shutter animation)
+	Common::Rect _quitArea;   // click area for quit button on Amiga/Atari console
 	void loadRigSprites(Common::SeekableReadStream *file, int sprigsOffset);
 	void loadIndicatorSprites(Common::SeekableReadStream *file, byte *palette,
-		int stepOffset, int angleOffset, int vehicleOffset);
+		int stepOffset, int angleOffset, int vehicleOffset, int quitOffset);
 
 	// Compass indicators loaded from executable
 	Graphics::ManagedSurface *_compassPitchStrip;  // pitch: 32px wide × (144+29) rows scrolling strip




More information about the Scummvm-git-logs mailing list