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

neuromancer noreply at scummvm.org
Mon Aug 17 19:06:03 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:
84e34fb7fe COLONY: added missing teleport code and features
ff2bb42f74 COLONY: specific icon for teleport in minimap
ecf28674b9 COLONY: implemented warp doors
00bad4ec1d COLONY: restored additonal door animations
e0138d8b73 COLONY: specific icon for forklift in minimap
c937dc9f37 COLONY: better object discovery in minimap


Commit: 84e34fb7fe98bd0a603ac1f5dcbc6e663f8074cf
    https://github.com/scummvm/scummvm/commit/84e34fb7fe98bd0a603ac1f5dcbc6e663f8074cf
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: added missing teleport code and features

Changed paths:
    engines/colony/animation.cpp
    engines/colony/colony.h
    engines/colony/interaction.cpp
    engines/colony/map.cpp
    engines/colony/movement.cpp


diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index 0e5e3a28625..80e658234e7 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -197,6 +197,7 @@ const AnimColorEntry kAnimColors[] = {
 	{ "vanity",      kBMC_Vanity,     ARRAYSIZE(kBMC_Vanity) },
 	{ "reactor",     kBMC_Reactor,    ARRAYSIZE(kBMC_Reactor) },
 	{ "security",    kBMC_Security,   ARRAYSIZE(kBMC_Security) },
+	{ "tele",        kBMC_Teleport,   ARRAYSIZE(kBMC_Teleport) },
 	{ "teleporter",  kBMC_Teleport,   ARRAYSIZE(kBMC_Teleport) },
 	{ "teleporter2", kBMC_Teleport,   ARRAYSIZE(kBMC_Teleport) },
 	{ "slides",      kBMC_Creatures,  ARRAYSIZE(kBMC_Creatures) },
@@ -283,6 +284,7 @@ bool ColonyEngine::loadAnimation(const Common::String &name) {
 		{ "elev",   "elevator" },
 		{ "slides", "slideshow" },
 		{ "lift",   "lifter" },
+		{ "tele",   "teleporter" },
 		{ nullptr,  nullptr }
 	};
 
@@ -1449,6 +1451,8 @@ void ColonyEngine::handleAnimationClick(int item) {
 		handleAirlockClick(item);
 	} else if (_animationName == "elev" || _animationName == "elevator" || _animationName == "elevator2") {
 		handleElevatorClick(item);
+	} else if (_animationName == "tele" || _animationName == "teleporter2") {
+		handleTeleportClick(item);
 	} else if (_animationName == "controls") {
 		handleControlsClick(item);
 	} else if (_animationName == "forklift") {
@@ -2026,6 +2030,72 @@ void ColonyEngine::handleElevatorClick(int item) {
 	}
 }
 
+// DoTeleport(): the screen inverts while the transport sound runs.
+void ColonyEngine::flashTeleportBooth() {
+	int ox = _screenR.left + (_screenR.width() - 416) / 2;
+	ox = (ox / 8) * 8;
+	const int oy = _screenR.top + (_screenR.height() - 264) / 2;
+	const Common::Rect booth(ox, oy, ox + 416, oy + 264);
+
+	for (int i = 0; i < 8; i++) {
+		_gfx->fillRect(booth, (i & 1) ? _gfx->black() : _gfx->white());
+		_gfx->copyToScreen();
+		responsiveAnimationDelay(_system, 60);
+	}
+	drawAnimation();
+	_gfx->copyToScreen();
+}
+
+// DoTeleport(): stepping in and closing the door are separate clicks, and the
+// player can leave either stage without being transported.
+void ColonyEngine::handleTeleportClick(int item) {
+	if (getPlatform() == Common::kPlatformMacintosh) {
+		if (_animationName == "teleporter2") {
+			if (item == 2 && !_teleportDone) {
+				_sound->play(Sound::kTeleport);
+				flashTeleportBooth();
+				_teleportDone = true;
+				teleportPlayer();
+			} else if (item == 1 && objectState(2) == 6) {
+				_animationRunning = false;
+			}
+			return;
+		}
+
+		if (item == 1) {
+			if (!loadAnimation("teleporter2")) {
+				_animationRunning = false;
+				return;
+			}
+			setObjectState(2, 6);
+			drawAnimation();
+			_gfx->copyToScreen();
+		}
+		return;
+	}
+
+	// DOS keeps both stages in one file.
+	if (!_teleportInside) {
+		if (item == 3) {
+			_teleportInside = true;
+			setObjectOnOff(3, false);
+			setObjectOnOff(1, false);
+			drawAnimation();
+			_gfx->copyToScreen();
+		}
+		return;
+	}
+
+	if (item == 4 && !_teleportDone) {
+		_sound->play(Sound::kTeleport);
+		flashTeleportBooth();
+		_teleportDone = true;
+		teleportPlayer();
+	} else if (item == 2) {
+		_animationRunning = false;
+	}
+}
+
 void ColonyEngine::handleControlsClick(int item) {
 	switch (item) {
 	case 4: // Accelerator
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 1bc24cdaac9..c7ab80a88ab 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -484,6 +484,7 @@ public:
 	void clearPlayerCellMarker();
 	void setPlayerCellMarker();
 	bool playerIntersectsObjectFootprint(const Thing &obj, int xloc, int yloc) const;
+	bool playerStartsInsideObject(int rnum) const;
 	void cCommand(int xnew, int ynew, bool allowInteraction);
 	bool scrollInfo(const Graphics::Font *macFont = nullptr);
 	bool checkSkipRequested();
@@ -587,6 +588,7 @@ private:
 	uint32 _lastAnimUpdate = 0;
 	uint32 _lastWarningChimeTime = 0;
 	uint32 _lastCollisionSoundTime = 0;
+	int _bumpedObject = 0;
 	int _action0 = 0, _action1 = 0;
 	int _creature = 0;
 	bool _allGrow = false;
@@ -744,6 +746,9 @@ private:
 	bool patchMapFrom(const PassPatch &from, uint8 *mapdata);
 	void exitForklift();
 	void dropCarriedObject();
+	bool stepOutOfCell();
+	bool exitTeleport();
+	void teleportPlayer();
 	bool setDoorState(int x, int y, int direction, int state);
 	int openAdjacentDoors(int x, int y);
 	int goToDestination(const uint8 *map, Locate *pobject);
@@ -840,6 +845,10 @@ private:
 	int _airlockY = -1;
 	int _airlockDirection = -1;
 	bool _airlockTerminate = false;
+	int _teleportX = -1;
+	int _teleportY = -1;
+	bool _teleportInside = false;
+	bool _teleportDone = false;
 
 	void playIntro();
 	bool makeStars(const Common::Rect &r, int btn);
@@ -879,6 +888,8 @@ private:
 	void handleDoorClick(int item);
 	void handleAirlockClick(int item);
 	void handleElevatorClick(int item);
+	void handleTeleportClick(int item);
+	void flashTeleportBooth();
 	void handleControlsClick(int item);
 	void dolSprite(int index);
 	void moveObject(int index);
diff --git a/engines/colony/interaction.cpp b/engines/colony/interaction.cpp
index 16752c5d8d3..b0d89a3c6da 100644
--- a/engines/colony/interaction.cpp
+++ b/engines/colony/interaction.cpp
@@ -145,40 +145,13 @@ void ColonyEngine::interactWithObject(int objNum) {
 			}
 			break;
 		}
-		// fl==0 or fl==2: use the teleporter
-		_sound->play(Sound::kTeleport);
-		int targetLevelRaw = _mapData[x][y][4][2];
-		int targetX = _action0;
-		int targetY = _action1;
-		// Check if this teleporter was relocated via patch
-		PassPatch tp;
-		tp.level = _level;
-		tp.xindex = x;
-		tp.yindex = y;
-		uint8 patchData[5];
-		if (patchMapTo(tp, patchData)) {
-			targetLevelRaw = patchData[2];
-			targetX = patchData[3];
-			targetY = patchData[4];
-		}
-		const int targetLevel = (targetLevelRaw == 0) ? _level : targetLevelRaw;
-		if (targetLevel >= 100 || targetX <= 0 || targetX >= 31 || targetY <= 0 || targetY >= 31) {
-			inform("TELEPORTER INITIALIZATION FAILED", true);
-			break;
-		}
-		if (targetLevel != _level)
-			loadMap(targetLevel);
-		const int oldX = _me.xindex;
-		const int oldY = _me.yindex;
-		_me.xindex = targetX;
-		_me.yindex = targetY;
-		_me.xloc = (targetX << 8) + 128;
-		_me.yloc = (targetY << 8) + 128;
-		_me.ang = _me.look;
-		if (oldX >= 0 && oldX < 32 && oldY >= 0 && oldY < 32 &&
-				_robotArray[oldX][oldY] == kMeNum)
-			_robotArray[oldX][oldY] = 0;
-		setPlayerCellMarker();
+		// fl==0 or fl==2: step into the booth
+		_teleportX = x;
+		_teleportY = y;
+		_teleportInside = false;
+		_teleportDone = false;
+		if (loadAnimation("tele"))
+			playAnimation();
 		break;
 	}
 	case kObjDrawer:
@@ -312,6 +285,52 @@ void ColonyEngine::interactWithObject(int objNum) {
 	}
 }
 
+// GANIMATE.C TelePorted(): runs when the booth door closes, never on contact.
+void ColonyEngine::teleportPlayer() {
+	const int x = CLIP<int>(_teleportX, 0, 30);
+	const int y = CLIP<int>(_teleportY, 0, 30);
+
+	uint8 mapdata[5] = {};
+	PassPatch from = {};
+	from.level = (uint8)_level;
+	from.xindex = (uint8)x;
+	from.yindex = (uint8)y;
+
+	PassPatch to = {};
+	if (patchMapTo(from, mapdata)) {
+		// Carried here; it kept the destination it was built with.
+		to.level = mapdata[2];
+		to.xindex = mapdata[3];
+		to.yindex = mapdata[4];
+	} else {
+		to.level = _mapData[x][y][4][2];
+		to.xindex = _mapData[x][y][4][3];
+		to.yindex = _mapData[x][y][4][4];
+	}
+
+	// The booth at the far end may itself have been carried elsewhere.
+	if (!patchMapFrom(to, mapdata)) {
+		mapdata[2] = (uint8)to.level;
+		mapdata[3] = (uint8)to.xindex;
+		mapdata[4] = (uint8)to.yindex;
+	}
+
+	goToDestination(mapdata, &_me);
+
+	if (!exitTeleport()) {
+		// Text 65: no way out of the booth
+		doText(65, 0);
+		terminateGame(false);
+		return;
+	}
+
+	setPlayerCellMarker();
+
+	// Wired to nowhere, or to the eighth dimension: the player is scattered.
+	if (mapdata[2] == 100 || !(mapdata[2] || mapdata[3] || mapdata[4]))
+		terminateGame(true);
+}
+
 // shoot.c SetPower(): adjust player's 3 power levels and update display.
 // p0=weapons delta, p1=life delta, p2=armor delta.
 void ColonyEngine::setPower(int p0, int p1, int p2) {
diff --git a/engines/colony/map.cpp b/engines/colony/map.cpp
index 3de42026f6d..ed7263eeffe 100644
--- a/engines/colony/map.cpp
+++ b/engines/colony/map.cpp
@@ -164,6 +164,7 @@ void ColonyEngine::loadMap(int mnum) {
 	delete[] buffer;
 	_dynamicObjectBase = kStaticObjectStartIndex;
 	_robotNum = MAX<int>(_robotNum, (int)_objects.size() + 1);
+	_bumpedObject = 0; // object numbers are per-level
 	_level = mnum;
 	_me.type = kMeNum;
 
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index 31f1149f83f..76eea90edcc 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -348,6 +348,15 @@ void ColonyEngine::setPlayerCellMarker() {
 	_robotArray[_me.xindex][_me.yindex] = kMeNum;
 }
 
+// CHCKWALL.C never blocks a move that stays inside the cell you already occupy.
+// Footprints let the player be in one, so they must be able to walk back out.
+bool ColonyEngine::playerStartsInsideObject(int rnum) const {
+	if (rnum <= 0 || rnum > (int)_objects.size())
+		return false;
+	const Thing &obj = _objects[rnum - 1];
+	return obj.alive && playerIntersectsObjectFootprint(obj, _me.xloc, _me.yloc);
+}
+
 bool ColonyEngine::playerIntersectsObjectFootprint(const Thing &obj, int xloc, int yloc) const {
 	ObjectFootprint fp;
 	if (!objectFootprintForType(obj.type, fp))
@@ -550,7 +559,7 @@ int ColonyEngine::checkwall(int xnew, int ynew, Locate *pobject) {
 		if (yind2 == pobject->yindex) {
 			if (pobject == &_me) {
 				const int rnum = occupiedObjectAt(xnew, ynew, xind2, yind2, pobject);
-				if (rnum)
+				if (rnum && !playerStartsInsideObject(rnum))
 					return rnum;
 			}
 			pobject->dx = xnew - pobject->xloc;
@@ -1301,19 +1310,96 @@ void ColonyEngine::cCommand(int xnew, int ynew, bool allowInteraction) {
 	const int oldYIndex = _me.yindex;
 	const bool sameCellAttempt = ((xnew >> 8) == oldXIndex && (ynew >> 8) == oldYIndex);
 	const int robot = checkwall(xnew, ynew, &_me);
-	if (robot > 0 && allowInteraction)
-		interactWithObject(robot);
-	else if (robot)
-		playCollisionSound();
-	else if (sameCellAttempt && _me.xindex == oldXIndex && _me.yindex == oldYIndex &&
-			(_me.xloc != xnew || _me.yloc != ynew))
-		playCollisionSound();
+	if (robot > 0 && allowInteraction) {
+		// CCommand() ran once per key event; movement here is continuous, so latch
+		// the object until contact breaks or its message reopens every frame.
+		if (robot != _bumpedObject) {
+			_bumpedObject = robot;
+			interactWithObject(robot);
+		} else {
+			playCollisionSound();
+		}
+	} else {
+		_bumpedObject = 0;
+		if (robot)
+			playCollisionSound();
+		else if (sameCellAttempt && _me.xindex == oldXIndex && _me.yindex == oldYIndex &&
+				(_me.xloc != xnew || _me.yloc != ynew))
+			playCollisionSound();
+	}
 
 	setPlayerCellMarker();
 
 	_suppressCollisionSound = false;
 }
 
+// DOS Forward(): inch ahead along _me.ang until the player leaves the cell.
+bool ColonyEngine::stepOutOfCell() {
+	const int xindex = _me.xindex;
+	const int yindex = _me.yindex;
+
+	// clampToWalls() can pin the player short of the boundary, so cap the walk.
+	int guard = 16;
+	_me.type = 2; // temporary small collision type
+	while (_me.xindex == xindex && _me.yindex == yindex) {
+		if (--guard < 0 || checkwall(_me.xloc + _cost[_me.ang], _me.yloc + _sint[_me.ang], &_me)) {
+			_sound->play(Sound::kChime);
+			_me.type = kMeNum;
+			return false;
+		}
+	}
+	_me.type = kMeNum;
+	return true;
+}
+
+// DOS ExitTeleport(): walk clear of the arrival booth, trying each quarter turn,
+// then leave a booth behind. False = all four directions blocked.
+bool ColonyEngine::exitTeleport() {
+	const int xloc = _me.xloc;
+	const int yloc = _me.yloc;
+	const int xindex = _me.xindex;
+	const int yindex = _me.yindex;
+
+	// goToDestination() stamped this cell as the player's; occupiedObjectAt()
+	// would read that marker back as a blocker.
+	clearPlayerCellMarker();
+
+	_me.ang = 48;
+	bool out = false;
+	for (int tries = 0; tries < 4 && !out; tries++) {
+		out = stepOutOfCell();
+		if (!out) {
+			_me.xloc = xloc;
+			_me.yloc = yloc;
+			_me.xindex = xindex;
+			_me.yindex = yindex;
+			_me.ang += 64;
+		}
+	}
+	if (!out)
+		return false;
+
+	if (xindex < 0 || xindex >= 32 || yindex < 0 || yindex >= 32)
+		return true;
+
+	// The original always rebuilds the booth because it reloads the map; the port
+	// keeps its object table, so relink an existing one instead of duplicating it.
+	if (_robotArray[xindex][yindex] != 0)
+		return true;
+
+	for (uint i = 0; i < _objects.size() && i < 255; i++) {
+		const Thing &obj = _objects[i];
+		if (obj.alive && obj.type == kObjTeleport &&
+				obj.where.xindex == xindex && obj.where.yindex == yindex) {
+			_robotArray[xindex][yindex] = (uint8)(i + 1);
+			return true;
+		}
+	}
+
+	createObject(kObjTeleport, (xindex << 8) + 128, (yindex << 8) + 128, 0);
+	return true;
+}
+
 // DOS ExitFL(): step back one cell and drop the forklift.
 void ColonyEngine::exitForklift() {
 	if (_fl != 1)


Commit: ff2bb42f744008d43c85b1fcff62860a35fe4b3a
    https://github.com/scummvm/scummvm/commit/ff2bb42f744008d43c85b1fcff62860a35fe4b3a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: specific icon for teleport in minimap

Changed paths:
    engines/colony/colony.h
    engines/colony/ui.cpp


diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index c7ab80a88ab..05368a7989d 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -770,6 +770,7 @@ private:
 	void drawAutomap();
 	void changeAutomapZoom(bool zoomIn);
 	void drawAutomapCryoMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip);
+	void drawAutomapTeleportMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip);
 	void markVisited();
 	void automapCellCorner(int dx, int dy, int xloc, int yloc, int lExt, int tsin, int tcos, int ccx, int ccy, int &sx, int &sy);
 	void automapDrawWall(const Common::Rect &vp, int x1, int y1, int x2, int y2, uint32 color);
diff --git a/engines/colony/ui.cpp b/engines/colony/ui.cpp
index a43434a82de..df443477442 100644
--- a/engines/colony/ui.cpp
+++ b/engines/colony/ui.cpp
@@ -1003,6 +1003,27 @@ void ColonyEngine::drawAutomapCryoMarker(int x, int y, int halfSize, uint32 colo
 		_gfx->fillEllipse(x, y, cr, cr, color);
 }
 
+// Octagon with a waistband, echoing the booth shape drawn in the 3D view.
+void ColonyEngine::drawAutomapTeleportMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip) {
+	if (x < clip.left || x >= clip.right || y < clip.top || y >= clip.bottom)
+		return;
+
+	const int r = MAX(halfSize, 3);
+	const int k = MAX(r * 2 / 5, 1);
+	const int dx[8] = { -k,  k,  r,  r,  k, -k, -r, -r };
+	const int dy[8] = { -r, -r, -k,  k,  r,  r,  k, -k };
+	for (int i = 0; i < 8; i++) {
+		int x1 = x + dx[i], y1 = y + dy[i];
+		int x2 = x + dx[(i + 1) & 7], y2 = y + dy[(i + 1) & 7];
+		if (clipLineToRect(x1, y1, x2, y2, clip))
+			_gfx->drawLine(x1, y1, x2, y2, color);
+	}
+
+	int bx1 = x - r, by1 = y, bx2 = x + r, by2 = y;
+	if (clipLineToRect(bx1, by1, bx2, by2, clip))
+		_gfx->drawLine(bx1, by1, bx2, by2, color);
+}
+
 void ColonyEngine::drawAutomap() {
 	if (_level < 1 || _level > 8)
 		return;
@@ -1045,6 +1066,7 @@ void ColonyEngine::drawAutomap() {
 	const int markerR = scaleR(isMac ? 5 : 3, 2);
 	const int foodR = scaleR(isMac ? 3 : 2, 1);
 	const int cryoR = scaleR(isMac ? 7 : 5, 3);
+	const int teleR = scaleR(isMac ? 6 : 4, 3);
 
 	for (int dy = -radius; dy <= radius; dy++) {
 		for (int dx = -radius; dx <= radius; dx++) {
@@ -1072,12 +1094,15 @@ void ColonyEngine::drawAutomap() {
 
 			const int mx = (x0 + x2) >> 1;
 			const int my = (y0 + y2) >> 1;
-			// Cryo pods are static, so they map beyond the robot/egg radar range.
+			// Cryo pods and teleporters are static, so they map beyond the
+			// robot/egg radar range.
 			const bool inRadar = (ABS(dx) <= 6 && ABS(dy) <= 6);
 			const uint8 rnum = _robotArray[cx][cy];
 			if (rnum > 0 && rnum != kMeNum && rnum <= _objects.size() && _objects[rnum - 1].alive) {
 				if (_objects[rnum - 1].type == kObjCryo)
 					drawAutomapCryoMarker(mx, my, cryoR, lineColor, vp);
+				else if (_objects[rnum - 1].type == kObjTeleport)
+					drawAutomapTeleportMarker(mx, my, teleR, lineColor, vp);
 				else if (inRadar)
 					drawMiniMapMarker(mx, my, markerR, lineColor, isMac, &vp);
 			}


Commit: ecf28674b9e318587931569cf7100eece8e3f13c
    https://github.com/scummvm/scummvm/commit/ecf28674b9e318587931569cf7100eece8e3f13c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: implemented warp doors

Changed paths:
    engines/colony/movement.cpp


diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index 76eea90edcc..d064f95731e 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -746,6 +746,10 @@ int ColonyEngine::goToDestination(const uint8 *map, Locate *pobject) {
 	if (targetMap == 0 && targetX == 0 && targetY == 0)
 		return 1;
 
+	// GOTOMAP.C GoTo(): robots never follow a destination.
+	if (pobject->type == 0)
+		return 0;
+
 	if (targetMap == 127) {
 		if (pobject != &_me)
 			return 0;
@@ -814,8 +818,10 @@ int ColonyEngine::tryPassThroughFeature(int fromX, int fromY, int direction, Loc
 
 	switch (map[0]) {
 	case kWallFeatureDoor:
+		// GOTOMAP.C OpenDoor(): a door carries a destination like any other
+		// feature, so opening one can warp instead of stepping through.
 		if (map[1] == 0)
-			return 1; // already open  pass through
+			return goToDestination(map, pobject);
 		if (pobject != &_me)
 			return 0; // robots can't open doors
 		// DOS DoDoor: play door animation, player clicks handle to open
@@ -833,7 +839,7 @@ int ColonyEngine::tryPassThroughFeature(int fromX, int fromY, int direction, Loc
 			playAnimation();
 			if (_animationResult) {
 				setDoorState(fromX, fromY, direction, 0);
-				return 1; // pass through
+				return goToDestination(map, pobject);
 			}
 			return 0; // player didn't open the door
 		}


Commit: 00bad4ec1d3bf12fee823e52443aa9c0a066e1a1
    https://github.com/scummvm/scummvm/commit/00bad4ec1d3bf12fee823e52443aa9c0a066e1a1
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: restored additonal door animations

Changed paths:
    engines/colony/colony.h
    engines/colony/movement.cpp
    engines/colony/sound.cpp
    engines/colony/sound.h


diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 05368a7989d..0b083a64472 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -493,6 +493,8 @@ public:
 	void checkCenter();
 	void fallThroughHole();
 	void playTunnelEffect(bool falling);
+	int rideTunnel(const uint8 *map, Locate *pobject);
+	void doDnStairs();
 
 	void doText(int entry, int center);
 	void inform(const char *text, bool hold);
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index d064f95731e..678e125e5bc 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -880,19 +880,32 @@ int ColonyEngine::tryPassThroughFeature(int fromX, int fromY, int direction, Loc
 		}
 
 	case kWallFeatureUpStairs:
-	case kWallFeatureDnStairs:
-	case kWallFeatureTunnel: {
+	case kWallFeatureDnStairs: {
 		if (pobject != &_me)
-			return 0; // robots don't use stairs/tunnels
+			return 0; // robots don't use stairs
+
+		// UpStairs(): the forklift cannot be driven up a staircase.
+		if (map[0] == kWallFeatureUpStairs && _fl)
+			return 0;
 
-		// Play appropriate sound
-		if (map[0] == kWallFeatureDnStairs)
-			_sound->play(Sound::kClatter);
 		const int result = goToDestination(map, pobject);
+		if (map[0] == kWallFeatureDnStairs && _fl)
+			doDnStairs();
 		if (result == 2) {
 			debugC(1, kColonyDebugMove, "Level change via %s: level=%d pos=(%d,%d)",
-				map[0] == kWallFeatureUpStairs ? "upstairs" :
-				map[0] == kWallFeatureDnStairs ? "downstairs" : "tunnel",
+				map[0] == kWallFeatureUpStairs ? "upstairs" : "downstairs",
+				_level, pobject->xindex, pobject->yindex);
+		}
+		return result;
+	}
+
+	case kWallFeatureTunnel: {
+		if (pobject != &_me)
+			return 0;
+
+		const int result = rideTunnel(map, pobject);
+		if (result == 2) {
+			debugC(1, kColonyDebugMove, "Level change via tunnel: level=%d pos=(%d,%d)",
 				_level, pobject->xindex, pobject->yindex);
 		}
 		return result;
@@ -1071,11 +1084,12 @@ void ColonyEngine::playTunnelEffect(bool falling) {
 	int counter = falling ? 2 : kTunnelST[0];
 	int spd = 180 / counter;
 
-	_sound->play(Sound::kTunnel2);
+	// Tunnel1 runs under the ride, Tunnel2 on arrival.
+	_sound->play(Sound::kTunnel1);
 
 	for (int remaining = tunnelFrames; remaining > 0 && !shouldQuit(); ) {
 		if (!_sound->isPlaying())
-			_sound->play(Sound::kTunnel2);
+			_sound->play(Sound::kTunnel1);
 
 		if (macColor) {
 			fillTunnelPattern(_gfx, effectRect, fillFg, fillBg, _macColors[tunnelColor].pattern);
@@ -1187,6 +1201,28 @@ void ColonyEngine::playTunnelEffect(bool falling) {
 	}
 
 	_sound->stop();
+	_sound->play(Sound::kTunnel2);
+}
+
+// TUNNEL.C tunnel(FALSE): ride the subway, then arrive at the far station.
+int ColonyEngine::rideTunnel(const uint8 *map, Locate *pobject) {
+	const bool hasDestination = (map[2] || map[3] || map[4]);
+
+	playTunnelEffect(false);
+	const int result = goToDestination(map, pobject);
+
+	if (!hasDestination) {
+		terminateGame(false);
+		return 0;
+	}
+	return result;
+}
+
+// DoDnStairs(): only the forklift clatters down the steps.
+void ColonyEngine::doDnStairs() {
+	_sound->play(Sound::kClatter);
+	_gfx->fillRect(_screenR, _gfx->black());
+	_gfx->copyToScreen();
 }
 
 void ColonyEngine::fallThroughHole() {
diff --git a/engines/colony/sound.cpp b/engines/colony/sound.cpp
index 1c79e25bdb2..ae342bc7530 100644
--- a/engines/colony/sound.cpp
+++ b/engines/colony/sound.cpp
@@ -364,6 +364,7 @@ bool Sound::playMacSound(int soundID, bool loop) {
 	case kStop: resID = 29382; break;   // FULLSTOP
 	case kTeleport: resID = 9757; break;
 	case kSlug: resID = 8347; break;
+	case kTunnel1: resID = 16403; break;
 	case kTunnel2: resID = 17354; break;
 	case kLift: resID = 28521; break;
 	case kGlass: resID = 19944; break;
diff --git a/engines/colony/sound.h b/engines/colony/sound.h
index e09be141b08..a83212f3238 100644
--- a/engines/colony/sound.h
+++ b/engines/colony/sound.h
@@ -67,6 +67,7 @@ public:
 		kStop,
 		kTeleport,
 		kSlug,
+		kTunnel1,
 		kTunnel2,
 		kLift,
 		kDrop,


Commit: e0138d8b73a9b21fe23461fc197646607b7f7d3d
    https://github.com/scummvm/scummvm/commit/e0138d8b73a9b21fe23461fc197646607b7f7d3d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: specific icon for forklift in minimap

Changed paths:
    engines/colony/colony.h
    engines/colony/ui.cpp


diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 0b083a64472..1776207a826 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -773,6 +773,7 @@ private:
 	void changeAutomapZoom(bool zoomIn);
 	void drawAutomapCryoMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip);
 	void drawAutomapTeleportMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip);
+	void drawAutomapForkliftMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip);
 	void markVisited();
 	void automapCellCorner(int dx, int dy, int xloc, int yloc, int lExt, int tsin, int tcos, int ccx, int ccy, int &sx, int &sy);
 	void automapDrawWall(const Common::Rect &vp, int x1, int y1, int x2, int y2, uint32 color);
diff --git a/engines/colony/ui.cpp b/engines/colony/ui.cpp
index df443477442..5b6f1ed7295 100644
--- a/engines/colony/ui.cpp
+++ b/engines/colony/ui.cpp
@@ -1024,6 +1024,26 @@ void ColonyEngine::drawAutomapTeleportMarker(int x, int y, int halfSize, uint32
 		_gfx->drawLine(bx1, by1, bx2, by2, color);
 }
 
+// Open-topped carriage: the only marker with a gap, so it reads at any zoom.
+void ColonyEngine::drawAutomapForkliftMarker(int x, int y, int halfSize, uint32 color, const Common::Rect &clip) {
+	if (x < clip.left || x >= clip.right || y < clip.top || y >= clip.bottom)
+		return;
+
+	const int r = MAX(halfSize, 3);
+	const int seg[4][4] = {
+		{ -r, -r, -r,  r },
+		{  r, -r,  r,  r },
+		{ -r,  r,  r,  r },
+		{ -r,  0,  r,  0 }
+	};
+	for (int i = 0; i < 4; i++) {
+		int x1 = x + seg[i][0], y1 = y + seg[i][1];
+		int x2 = x + seg[i][2], y2 = y + seg[i][3];
+		if (clipLineToRect(x1, y1, x2, y2, clip))
+			_gfx->drawLine(x1, y1, x2, y2, color);
+	}
+}
+
 void ColonyEngine::drawAutomap() {
 	if (_level < 1 || _level > 8)
 		return;
@@ -1067,6 +1087,7 @@ void ColonyEngine::drawAutomap() {
 	const int foodR = scaleR(isMac ? 3 : 2, 1);
 	const int cryoR = scaleR(isMac ? 7 : 5, 3);
 	const int teleR = scaleR(isMac ? 6 : 4, 3);
+	const int forkR = scaleR(isMac ? 6 : 4, 3);
 
 	for (int dy = -radius; dy <= radius; dy++) {
 		for (int dx = -radius; dx <= radius; dx++) {
@@ -1094,8 +1115,8 @@ void ColonyEngine::drawAutomap() {
 
 			const int mx = (x0 + x2) >> 1;
 			const int my = (y0 + y2) >> 1;
-			// Cryo pods and teleporters are static, so they map beyond the
-			// robot/egg radar range.
+			// Cryo pods, teleporters and the forklift are static, so they map
+			// beyond the robot/egg radar range.
 			const bool inRadar = (ABS(dx) <= 6 && ABS(dy) <= 6);
 			const uint8 rnum = _robotArray[cx][cy];
 			if (rnum > 0 && rnum != kMeNum && rnum <= _objects.size() && _objects[rnum - 1].alive) {
@@ -1103,6 +1124,8 @@ void ColonyEngine::drawAutomap() {
 					drawAutomapCryoMarker(mx, my, cryoR, lineColor, vp);
 				else if (_objects[rnum - 1].type == kObjTeleport)
 					drawAutomapTeleportMarker(mx, my, teleR, lineColor, vp);
+				else if (_objects[rnum - 1].type == kObjForkLift)
+					drawAutomapForkliftMarker(mx, my, forkR, lineColor, vp);
 				else if (inRadar)
 					drawMiniMapMarker(mx, my, markerR, lineColor, isMac, &vp);
 			}


Commit: c937dc9f3762f532770b89705db95d6d8efe0a85
    https://github.com/scummvm/scummvm/commit/c937dc9f3762f532770b89705db95d6d8efe0a85
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-17T21:05:37+02:00

Commit Message:
COLONY: better object discovery in minimap

Changed paths:
    engines/colony/ui.cpp


diff --git a/engines/colony/ui.cpp b/engines/colony/ui.cpp
index 5b6f1ed7295..229580198ee 100644
--- a/engines/colony/ui.cpp
+++ b/engines/colony/ui.cpp
@@ -937,26 +937,14 @@ void ColonyEngine::automapDrawWall(const Common::Rect &vp, int x1, int y1, int x
 	}
 }
 
+// Own side only: a feature opens from the cell that records it, so the far side
+// is a plain wall with nothing to use.
 int ColonyEngine::automapWallFeature(int fx, int fy, int dir) {
-	if (fx >= 0 && fx < 31 && fy >= 0 && fy < 31) {
-		int feat = _mapData[fx][fy][dir][0];
-		if (isPassableFeature(feat))
-			return feat;
-	}
-	int nx = fx, ny = fy, opp = -1;
-	switch (dir) {
-	case kDirNorth: ny = fy + 1; opp = kDirSouth; break;
-	case kDirSouth: ny = fy - 1; opp = kDirNorth; break;
-	case kDirEast:  nx = fx + 1; opp = kDirWest;  break;
-	case kDirWest:  nx = fx - 1; opp = kDirEast;  break;
-	default: return 0;
-	}
-	if (nx >= 0 && nx < 31 && ny >= 0 && ny < 31) {
-		int feat = _mapData[nx][ny][opp][0];
-		if (isPassableFeature(feat))
-			return feat;
-	}
-	return 0;
+	if (fx < 0 || fx >= 31 || fy < 0 || fy >= 31)
+		return 0;
+
+	const int feat = _mapData[fx][fy][dir][0];
+	return isPassableFeature(feat) ? feat : 0;
 }
 
 void ColonyEngine::automapDrawWallWithFeature(const Common::Rect &vp, int wx1, int wy1, int wx2, int wy2, int feat, int lExt, uint32 color) {
@@ -970,6 +958,21 @@ void ColonyEngine::automapDrawWallWithFeature(const Common::Rect &vp, int wx1, i
 		if (plen > 0) {
 			const int tx = (ppx * tickLen) / plen;
 			const int ty = (ppy * tickLen) / plen;
+			if (feat == kWallFeatureUpStairs || feat == kWallFeatureDnStairs) {
+				// A flight of steps, as on a floor plan.
+				const int mx = (wx1 + wx2) / 2;
+				const int my = (wy1 + wy2) / 2;
+				const int hx = (wx2 - wx1) / 4;
+				const int hy = (wy2 - wy1) / 4;
+				for (int i = -2; i <= 2; i++) {
+					if (i == 0)
+						continue;
+					const int ox = mx + tx * i;
+					const int oy = my + ty * i;
+					automapDrawWall(vp, ox - hx, oy - hy, ox + hx, oy + hy, color);
+				}
+				return;
+			}
 			const int ax = wx1 + (wx2 - wx1) / 4;
 			const int ay = wy1 + (wy2 - wy1) / 4;
 			const int bx = wx1 + 3 * (wx2 - wx1) / 4;




More information about the Scummvm-git-logs mailing list