[Scummvm-git-logs] scummvm master -> 4d591f74db47b3a7a2f7e709bec7fc397b85b456

neuromancer noreply at scummvm.org
Wed May 27 06:56:16 UTC 2026


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

Summary:
5746cd0360 FREESCAPE: precise implementation for SPFX (amiga/atari)
4d591f74db FREESCAPE: precise implementation for drill placement restrictions in driller


Commit: 5746cd036035364fd17f6d6a69a9cfea16387fbb
    https://github.com/scummvm/scummvm/commit/5746cd036035364fd17f6d6a69a9cfea16387fbb
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-05-27T08:56:01+02:00

Commit Message:
FREESCAPE: precise implementation for SPFX (amiga/atari)

Changed paths:
    engines/freescape/gfx.cpp
    engines/freescape/language/instruction.cpp


diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 532d325d4ae..16423e02ca6 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -1305,10 +1305,15 @@ void Renderer::drawBackground(uint8 color) {
 	uint8 r2, g2, b2;
 
 	if (_colorRemaps && _colorRemaps->contains(color)) {
-		color = (*_colorRemaps)[color];
-		if (_renderMode == Common::kRenderCPC && isEncodedCPCDirectColor(color))
-			color = decodeCPCDirectColor(color);
-		readFromPalette(color, r1, g1, b1);
+		int mappedColor = (*_colorRemaps)[color];
+		if (_renderMode == Common::kRenderAmiga || _renderMode == Common::kRenderAtariST)
+			_texturePixelFormat.colorToRGB(mappedColor, r1, g1, b1);
+		else {
+			color = mappedColor;
+			if (_renderMode == Common::kRenderCPC && isEncodedCPCDirectColor(color))
+				color = decodeCPCDirectColor(color);
+			readFromPalette(color, r1, g1, b1);
+		}
 		clear(r1, g1, b1);
 		return;
 	}
diff --git a/engines/freescape/language/instruction.cpp b/engines/freescape/language/instruction.cpp
index 5d93deced55..ccff0398fba 100644
--- a/engines/freescape/language/instruction.cpp
+++ b/engines/freescape/language/instruction.cpp
@@ -410,51 +410,120 @@ void FreescapeEngine::executePrint(FCLInstruction &instruction) {
 	_currentAreaMessages.push_back(_messagesList[index]);
 }
 
+uint32 spfxBasePaletteColor(FreescapeEngine *engine, uint8 index) {
+	index &= 0x0f;
+	uint8 r = engine->_gfx->_palette[3 * index + 0];
+	uint8 g = engine->_gfx->_palette[3 * index + 1];
+	uint8 b = engine->_gfx->_palette[3 * index + 2];
+	return engine->_gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+}
+
+uint32 spfxDirectPaletteColor(FreescapeEngine *engine, uint16 value) {
+	uint8 r = (value >> 8) & 0x0f;
+	uint8 g = (value >> 4) & 0x0f;
+	uint8 b = value & 0x0f;
+
+	if (engine->isAtariST()) {
+		r = ((r & 0x07) << 1) | ((r & 0x07) >> 2);
+		g = ((g & 0x07) << 1) | ((g & 0x07) >> 2);
+		b = ((b & 0x07) << 1) | ((b & 0x07) >> 2);
+	}
+
+	r = (r << 4) | r;
+	g = (g << 4) | g;
+	b = (b << 4) | b;
+	return engine->_gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+}
+
+uint32 spfxActivePaletteColor(FreescapeEngine *engine, uint8 index) {
+	index &= 0x0f;
+	if (engine->_currentArea->_colorRemaps.contains(index))
+		return (uint32)engine->_currentArea->_colorRemaps[index];
+	return spfxBasePaletteColor(engine, index);
+}
+
+void spfxSetActivePaletteColor(FreescapeEngine *engine, uint8 index, uint32 color) {
+	index &= 0x0f;
+	if (color == spfxBasePaletteColor(engine, index))
+		engine->_currentArea->unremapColor(index);
+	else
+		engine->_currentArea->remapColor(index, color);
+}
+
+void spfxFillRange(FreescapeEngine *engine, uint8 start, uint8 end, uint32 color) {
+	if (end < start)
+		return;
+
+	for (int i = start; i <= end; i++)
+		spfxSetActivePaletteColor(engine, i, color);
+}
+
+void spfxRestoreRange(FreescapeEngine *engine, uint8 start, uint8 end) {
+	if (end < start)
+		return;
+
+	for (int i = start; i <= end; i++)
+		engine->_currentArea->unremapColor(i);
+}
+
+void spfxRotateLeft(FreescapeEngine *engine, uint8 start, uint8 end) {
+	if (end <= start)
+		return;
+
+	uint32 color = spfxActivePaletteColor(engine, start);
+	for (int i = start; i < end; i++)
+		spfxSetActivePaletteColor(engine, i, spfxActivePaletteColor(engine, i + 1));
+	spfxSetActivePaletteColor(engine, end, color);
+}
+
+void spfxRotateRight(FreescapeEngine *engine, uint8 start, uint8 end) {
+	if (end <= start)
+		return;
+
+	uint32 color = spfxActivePaletteColor(engine, end);
+	for (int i = end; i > start; i--)
+		spfxSetActivePaletteColor(engine, i, spfxActivePaletteColor(engine, i - 1));
+	spfxSetActivePaletteColor(engine, start, color);
+}
+
 void FreescapeEngine::executeSPFX(FCLInstruction &instruction) {
 	uint16 src = instruction._source;
 	uint16 dst = instruction._destination;
 	if (isAmiga() || isAtariST()) {
-		uint8 r = 0;
-		uint8 g = 0;
-		uint8 b = 0;
-		uint32 color = 0;
-
-		if (src == 2 && dst == 0) {
-			// The Amiga interpreter handles SPFX $0200 by restoring the current area palette.
-			_currentArea->_colorRemaps.clear();
-		} else if (src & (1 << 7)) {
-			uint16 v = 0;
-			color = 0;
-			// Extract the color to replace from the src/dst values
-			v = (src & 0x77) << 8;
-			v = v | (dst & 0x70);
-			v = v >> 4;
-
-			// Convert the color to RGB
-			r = (v & 0xf00) >> 8;
-			r = r << 4 | r;
-			r = r & 0xff;
-
-			g = (v & 0xf0) >> 4;
-			g = g << 4 | g;
-			g = g & 0xff;
-
-			b = v & 0xf;
-			b = b << 4 | b;
-			b = b & 0xff;
-
-			color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
-			_currentArea->remapColor(dst & 0x0f, color); // src & 0x77, dst & 0x0f
-		} else if ((src & 0xf0) >> 4 == 1) {
-			_gfx->readFromPalette(src & 0x0f, r, g, b);
-			color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
-			for (int i = 1; i < 16; i++)
-				_currentArea->remapColor(i, color);
-		} else if ((src & 0x0f) == 1) {
-			_gfx->readFromPalette(dst & 0x0f, r, g, b);
-			color = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
-			for (int i = 1; i < 16; i++)
-				_currentArea->remapColor(i, color);
+		uint16 raw = ((src & 0xff) << 8) | (dst & 0xff);
+		if (raw & 0x8000) {
+			uint16 color = raw & 0x7770;
+			if (isAmiga())
+				color >>= 3;
+			else
+				color >>= 4;
+
+			spfxSetActivePaletteColor(this, raw & 0x0f, spfxDirectPaletteColor(this, color));
+		} else if ((raw & 0xf000) == 0x1000) {
+			spfxFillRange(this, (raw >> 4) & 0x0f, raw & 0x0f, spfxBasePaletteColor(this, (raw >> 8) & 0x0f));
+		} else {
+			switch (raw & 0x0f00) {
+			case 0x0000:
+				spfxSetActivePaletteColor(this, raw & 0x0f, spfxBasePaletteColor(this, (raw >> 4) & 0x0f));
+				break;
+			case 0x0100:
+				spfxFillRange(this, 0, 14, spfxBasePaletteColor(this, raw & 0x0f));
+				break;
+			case 0x0200:
+				_currentArea->_colorRemaps.clear();
+				break;
+			case 0x0300:
+				spfxRestoreRange(this, (raw >> 4) & 0x0f, raw & 0x0f);
+				break;
+			case 0x0400:
+				spfxRotateLeft(this, (raw >> 4) & 0x0f, raw & 0x0f);
+				break;
+			case 0x0500:
+				spfxRotateRight(this, (raw >> 4) & 0x0f, raw & 0x0f);
+				break;
+			default:
+				break;
+			}
 		}
 	} else {
 		debugC(1, kFreescapeDebugCode, "Switching palette from position %d to %d", src, dst);


Commit: 4d591f74db47b3a7a2f7e709bec7fc397b85b456
    https://github.com/scummvm/scummvm/commit/4d591f74db47b3a7a2f7e709bec7fc397b85b456
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-05-27T08:56:01+02:00

Commit Message:
FREESCAPE: precise implementation for drill placement restrictions in driller

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


diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index b67c28b4b59..bb8b0e16a72 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -81,11 +81,6 @@ DrillerEngine::DrillerEngine(OSystem *syst, const ADGameDescription *gd) : Frees
 	_maxEnergy = 63;
 	_maxShield = 63;
 
-	Math::Vector3d drillBaseOrigin = Math::Vector3d(0, 0, 0);
-	Math::Vector3d drillBaseSize = Math::Vector3d(3, 2, 3);
-	_drillBase = new GeometricObject(kCubeType, 0, 0, drillBaseOrigin, drillBaseSize, nullptr, nullptr, nullptr, FCLInstructionVector(), "");
-	assert(!_drillBase->isDestroyed() && !_drillBase->isInvisible());
-
 	if (isDemo()) {
 		_demoMode = !_disableDemoMode; // Most of the driller demos are non-interactive
 		_angleRotationIndex = 0;
@@ -106,7 +101,6 @@ DrillerEngine::DrillerEngine(OSystem *syst, const ADGameDescription *gd) : Frees
 DrillerEngine::~DrillerEngine() {
 	delete _playerMusic;
 	delete _playerC64Sfx;
-	delete _drillBase;
 
 	if (_borderExtra) {
 		delete _borderExtra;
@@ -585,15 +579,21 @@ void DrillerEngine::pressedKey(const int keycode) {
 		}
 
 		_gameStateVars[k8bitVariableEnergy] = _gameStateVars[k8bitVariableEnergy] - 5;
-		const Math::Vector3d gasPocket3D(gasPocket.x, drillCenter.y(), gasPocket.y);
-		const float distanceToPocket = (gasPocket3D - drillCenter).length();
+		const int areaScale = MAX<int>(_currentArea->getScale(), 1);
+		const int drillRawX = int(drillCenter.x() * areaScale / 32.0f);
+		const int drillRawZ = int(drillCenter.z() * areaScale / 32.0f);
+		const int gasRawX = gasPocket.x / 32;
+		const int gasRawZ = gasPocket.y / 32;
+		const int gasRadiusRaw = MAX<int>(gasPocketRadius / 32, 1);
+		// BTF6 scores the rig from raw-cell Manhattan distance, not Euclidean distance.
+		const uint distanceToPocket = ABS(gasRawX - drillRawX) + ABS(gasRawZ - drillRawZ);
 		debugC(1, kFreescapeDebugMove, "DRILL gas pocket raw=(%d,%d,r=%u) world=(%d,%d) radius=%u",
 			gasPocket.x / 32, gasPocket.y / 32, gasPocketRadius / 32, gasPocket.x, gasPocket.y, gasPocketRadius);
-		debugC(1, kFreescapeDebugMove, "DRILL gas distance renderCenter=(%.2f,%.2f) gasWorld=(%d,%d) euclidean=%.2f worldRadius=%u",
-			drillCenter.x(), drillCenter.z(), gasPocket.x, gasPocket.y, distanceToPocket, gasPocketRadius);
+		debugC(1, kFreescapeDebugMove, "DRILL gas distance renderCenter=(%.2f,%.2f) rawCenter=(%d,%d) gasRaw=(%d,%d) manhattan=%u rawRadius=%d",
+			drillCenter.x(), drillCenter.z(), drillRawX, drillRawZ, gasRawX, gasRawZ, distanceToPocket, gasRadiusRaw);
 
-		float success = _useAutomaticDrilling ? 100.0f : 100.0f * (1.0f - distanceToPocket / gasPocketRadius);
-		debugC(1, kFreescapeDebugMove, "DRILL gas computed success=%.2f automatic=%d", success, _useAutomaticDrilling ? 1 : 0);
+		int success = _useAutomaticDrilling ? 100 : 100 - int((100 * distanceToPocket) / gasRadiusRaw);
+		debugC(1, kFreescapeDebugMove, "DRILL gas computed success=%d automatic=%d", success, _useAutomaticDrilling ? 1 : 0);
 		// Play the "processing" sound up front (matches BTF660 in the
 		// original Amiga code, where sound 5 starts before the
 		// RIG POSITIONED / NO GAS FOUND messages are displayed and
@@ -615,15 +615,15 @@ void DrillerEngine::pressedKey(const int keycode) {
 		maxScoreMessage.replace(2, 6, Common::String::format("%d", maxScore));
 		insertTemporaryMessage(maxScoreMessage, _countdown - 4);
 		Common::String successMessage = _messagesList[6];
-		successMessage.replace(0, 4, Common::String::format("%d", int(success)));
+		successMessage.replace(0, 4, Common::String::format("%d", success));
 		while (successMessage.size() < 14)
 			successMessage += " ";
 		insertTemporaryMessage(successMessage, _countdown - 6);
 		_drillSuccessByArea[_currentArea->getAreaID()] = uint32(success);
 		_gameStateVars[k8bitVariableScore] += uint32(maxScore * uint32(success)) / 100;
-		debugC(1, kFreescapeDebugMove, "DRILL result: gas found success=%.2f maxScore=%d score=%d", success, maxScore, _gameStateVars[k8bitVariableScore]);
+		debugC(1, kFreescapeDebugMove, "DRILL result: gas found success=%d maxScore=%d score=%d", success, maxScore, _gameStateVars[k8bitVariableScore]);
 
-		if (success >= 50.0f) {
+		if (success >= 50) {
 			_drillStatusByArea[_currentArea->getAreaID()] = kDrillerRigInPlace;
 			_gameStateVars[32]++;
 		} else
@@ -710,88 +710,87 @@ bool DrillerEngine::drillDeployed(Area *area) {
 }
 
 bool DrillerEngine::checkDrill(const Math::Vector3d position) {
-	GeometricObject *obj = nullptr;
-	Math::Vector3d origin = position;
-
-	int16 id;
-	int heightLastObject;
-
-	origin.setValue(0, origin.x() + 128);
-	origin.setValue(1, origin.y() - 5);
-	origin.setValue(2, origin.z() + 128);
-
-	_drillBase->setOrigin(origin);
-	ObjectArray collisions = _currentArea->checkCollisions(_drillBase->_boundingBox);
-	if (collisions.empty())
-		return false;
-
-	origin.setValue(0, origin.x() - 128);
-	origin.setValue(2, origin.z() - 128);
-
-	id = 255;
-	obj = (GeometricObject *)_areaMap[255]->objectWithID(id);
-	assert(obj);
-	obj = (GeometricObject *)obj->duplicate();
-	origin.setValue(1, origin.y() + 6);
-	obj->setOrigin(origin);
-
-	// This bounding box is too large and can result in the drill to float next to a wall
-	collisions = _currentArea->checkCollisions(obj->_boundingBox);
-	if (!collisions.empty())
-		return false;
-
-	origin.setValue(1, origin.y() + 15);
-	obj->setOrigin(origin);
-
-	collisions = _currentArea->checkCollisions(obj->_boundingBox);
-	if (!collisions.empty())
+	const int areaScale = MAX<int>(_currentArea->getScale(), 1);
+	const float rawScale = areaScale / 32.0f;
+	const int drillCenterX = int(position.x() * rawScale);
+	const int drillCenterZ = int((position.z() + 128.0f) * rawScale);
+	const int drillY = int(position.y() * rawScale);
+
+	// BTF604/BTF607 validate a 12-cell footprint against raw room bounds and cube objects.
+	if (drillCenterX <= 6 || drillCenterX >= 121 || drillCenterZ <= 6 || drillCenterZ >= 121) {
+		debugC(1, kFreescapeDebugMove, "DRILL rejected: original footprint bounds failed rawCenter=(%d,%d)", drillCenterX, drillCenterZ);
 		return false;
+	}
 
-	origin.setValue(1, origin.y() - 10);
-	heightLastObject = obj->getSize().y();
-	delete obj;
-
-	id = 254;
-	debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id);
-	obj = (GeometricObject *)_areaMap[255]->objectWithID(id);
-	assert(obj);
-	// Set position for object
-	origin.setValue(0, origin.x() - obj->getSize().x() / 5);
-	origin.setValue(1, origin.y() + heightLastObject);
-	origin.setValue(2, origin.z() - obj->getSize().z() / 5);
-
-	obj = (GeometricObject *)obj->duplicate();
-	obj->setOrigin(origin);
-	collisions = _currentArea->checkCollisions(obj->_boundingBox);
-	if (!collisions.empty())
-		return false;
+	if (drillY != 0) {
+		const int floorX = drillCenterX - 1;
+		const int floorZ = drillCenterZ - 1;
+		bool supported = false;
+		ObjectMap *objects = _currentArea->getObjectsByID();
+		for (auto &it : *objects) {
+			Object *obj = it._value;
+			if (obj->isDestroyed() || obj->isInvisible() || obj->getType() != kCubeType)
+				continue;
+
+			GeometricObject *gobj = (GeometricObject *)obj;
+			const int objX = int(gobj->getOrigin().x() * rawScale);
+			const int objY = int(gobj->getOrigin().y() * rawScale);
+			const int objZ = int(gobj->getOrigin().z() * rawScale);
+			const int objSizeX = int(gobj->getSize().x() * rawScale);
+			const int objSizeY = int(gobj->getSize().y() * rawScale);
+			const int objSizeZ = int(gobj->getSize().z() * rawScale);
+
+			if (objY + objSizeY != drillY)
+				continue;
+			if (floorX < objX || floorZ < objZ)
+				continue;
+			if (floorX > objX + objSizeX - 2)
+				continue;
+			if (floorZ <= objZ + objSizeZ - 2) {
+				supported = true;
+				break;
+			}
+		}
 
-	// Undo offset
-	origin.setValue(0, origin.x() + obj->getSize().x() / 5);
-	heightLastObject = obj->getSize().y();
-	origin.setValue(2, origin.z() + obj->getSize().z() / 5);
-	delete obj;
+		if (!supported) {
+			debugC(1, kFreescapeDebugMove, "DRILL rejected: original floor support failed rawFloor=(%d,%d,%d)", floorX, drillY, floorZ);
+			return false;
+		}
+	}
 
-	id = 253;
-	debugC(1, kFreescapeDebugParser, "Adding object %d to room structure", id);
-	obj = (GeometricObject *)_areaMap[255]->objectWithID(id);
-	assert(obj);
-	obj = (GeometricObject *)obj->duplicate();
+	const int footprintOffset = drillY == 0 ? 5 : 6;
+	const int footprintX = drillCenterX - footprintOffset;
+	const int footprintZ = drillCenterZ - footprintOffset;
+	ObjectMap *objects = _currentArea->getObjectsByID();
+	for (auto &it : *objects) {
+		Object *obj = it._value;
+		if (obj->isDestroyed() || obj->isInvisible() || obj->getType() != kCubeType)
+			continue;
 
-	origin.setValue(0, origin.x() + obj->getSize().x() / 5);
-	origin.setValue(1, origin.y() + heightLastObject);
-	origin.setValue(2, origin.z() + obj->getSize().z() / 5);
+		GeometricObject *gobj = (GeometricObject *)obj;
+		const int objX = int(gobj->getOrigin().x() * rawScale);
+		const int objY = int(gobj->getOrigin().y() * rawScale);
+		const int objZ = int(gobj->getOrigin().z() * rawScale);
+		const int objSizeX = int(gobj->getSize().x() * rawScale);
+		const int objSizeY = int(gobj->getSize().y() * rawScale);
+		const int objSizeZ = int(gobj->getSize().z() * rawScale);
 
-	obj->setOrigin(origin);
-	collisions = _currentArea->checkCollisions(obj->_boundingBox);
-	if (!collisions.empty())
-		return false;
+		if (objY + objSizeY <= drillY)
+			continue;
+		if (footprintX < objX - 11)
+			continue;
+		if (drillY < objY - 22)
+			continue;
+		if (footprintZ < objZ - 11)
+			continue;
+		if (footprintX >= objX + objSizeX)
+			continue;
+		if (footprintZ < objZ + objSizeZ) {
+			debugC(1, kFreescapeDebugMove, "DRILL rejected: original object footprint hit obj=%d rawFootprint=(%d,%d,%d)", obj->getObjectID(), footprintX, drillY, footprintZ);
+			return false;
+		}
+	}
 
-	// Undo offset
-	// origin.setValue(0, origin.x() - obj->getSize().x() / 5);
-	heightLastObject = obj->getSize().y();
-	// origin.setValue(2, origin.z() - obj->getSize().z() / 5);
-	delete obj;
 	return true;
 }
 
diff --git a/engines/freescape/games/driller/driller.h b/engines/freescape/games/driller/driller.h
index 3041a23c5fe..408826ce7a4 100644
--- a/engines/freescape/games/driller/driller.h
+++ b/engines/freescape/games/driller/driller.h
@@ -82,7 +82,6 @@ private:
 	int _finalAreaWinConditionIndex;
 	int _amigaAtariEndGameStep;
 	bool drillDeployed(Area *area);
-	GeometricObject *_drillBase;
 	Math::Vector3d drillPosition();
 	float compassYaw() const;
 	void addDrill(const Math::Vector3d position, bool gasFound);




More information about the Scummvm-git-logs mailing list