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

neuromancer noreply at scummvm.org
Sat Aug 22 10:00:03 UTC 2026


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

Summary:
534c32850a COLONY: added varios commands to test goals and game ending
d8df792d60 COLONY: adjusted message displaying to match the original sources
cff4e56a26 SCUMM: RA: manually initialize _vm->_actionMap


Commit: 534c32850a7a8ecbb470e5daca4cc9fafe9a65a0
    https://github.com/scummvm/scummvm/commit/534c32850a7a8ecbb470e5daca4cc9fafe9a65a0
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-22T11:59:36+02:00

Commit Message:
COLONY: added varios commands to test goals and game ending

Changed paths:
    engines/colony/colony.h
    engines/colony/console.cpp
    engines/colony/console.h
    engines/colony/intro.cpp


diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index ab065095e88..b159fe44698 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -898,6 +898,7 @@ private:
 	void takeOff();
 	void fullOfStars();
 	void gameOver(bool kill);
+	void gameOver(bool kill, int savedCryos);
 	int countSavedCryos() const;
 	void playAnimation();
 	void updateAnimation();
diff --git a/engines/colony/console.cpp b/engines/colony/console.cpp
index e64cb2f89bd..a109fe08579 100644
--- a/engines/colony/console.cpp
+++ b/engines/colony/console.cpp
@@ -74,10 +74,47 @@ const char *featureTypeName(int type) {
 	}
 }
 
+const char *reactorStateName(int state) {
+	switch (state) {
+	case 0: return "closed, core installed";
+	case 1: return "open, core installed";
+	case 2: return "open, core removed";
+	default: return "unknown";
+	}
+}
+
+const char *reactorPowerName(int power) {
+	switch (power) {
+	case 0: return "off";
+	case 1: return "emergency";
+	case 2: return "full";
+	default: return "unknown";
+	}
+}
+
+struct NumberedTeleporter {
+	int x;
+	int y;
+	int destination;
+};
+
+// MAP.7 contains the four working booths. Their destinations establish the
+// original numbering: 1 -> 2 -> 3 -> 4 -> nowhere.
+const NumberedTeleporter kNumberedTeleporters[] = {
+	{9, 3, 2},
+	{3, 3, 3},
+	{3, 9, 4},
+	{9, 9, 0}
+};
+
 Debugger::Debugger(ColonyEngine *vm) : GUI::Debugger(), _vm(vm) {
 	registerCmd("teleport", WRAP_METHOD(Debugger, cmdTeleport));
+	registerCmd("teleporter", WRAP_METHOD(Debugger, cmdTeleporter));
+	registerCmd("teleporters", WRAP_METHOD(Debugger, cmdTeleporters));
 	registerCmd("pos", WRAP_METHOD(Debugger, cmdPos));
 	registerCmd("info", WRAP_METHOD(Debugger, cmdInfo));
+	registerCmd("goals", WRAP_METHOD(Debugger, cmdGoals));
+	registerCmd("win", WRAP_METHOD(Debugger, cmdWin));
 	registerCmd("robots", WRAP_METHOD(Debugger, cmdRobots));
 	registerCmd("map", WRAP_METHOD(Debugger, cmdMap));
 	registerCmd("give", WRAP_METHOD(Debugger, cmdGive));
@@ -89,6 +126,192 @@ Debugger::Debugger(ColonyEngine *vm) : GUI::Debugger(), _vm(vm) {
 	registerCmd("spawn", WRAP_METHOD(Debugger, cmdSpawn));
 }
 
+void Debugger::postEnter() {
+	GUI::Debugger::postEnter();
+
+	if (_pendingEnding == 0)
+		return;
+
+	const int ending = _pendingEnding;
+	_pendingEnding = 0;
+	const bool destroyPlanet = ending <= 3;
+	const int savedCryos = (ending == 1 || ending == 4) ? 6 :
+		((ending == 2 || ending == 5) ? 1 : 0);
+	_vm->gameOver(destroyPlanet, savedCryos);
+}
+
+bool Debugger::getTeleporterLocation(int number, int &level, int &x, int &y) const {
+	const NumberedTeleporter &teleporter = kNumberedTeleporters[number - 1];
+	level = 7;
+	x = teleporter.x;
+	y = teleporter.y;
+
+	for (uint i = 0; i < _vm->_patches.size(); i++) {
+		const PatchEntry &patch = _vm->_patches[i];
+		if (patch.type == kObjTeleport && patch.from.level == 7 &&
+				patch.from.xindex == teleporter.x && patch.from.yindex == teleporter.y) {
+			level = patch.to.level;
+			x = patch.to.xindex;
+			y = patch.to.yindex;
+			return true;
+		}
+	}
+	return false;
+}
+
+bool Debugger::cmdTeleporters(int argc, const char **argv) {
+	debugPrintf("Numbered teleporter series (1 -> 2 -> 3 -> 4):\n");
+	for (uint i = 0; i < ARRAYSIZE(kNumberedTeleporters); i++) {
+		int level, x, y;
+		getTeleporterLocation(i + 1, level, x, y);
+		if (level == 100) {
+			debugPrintf("  %u: carried by the forklift", i + 1);
+		} else {
+			debugPrintf("  %u: level %d at (%d, %d)", i + 1, level, x, y);
+		}
+
+		if (kNumberedTeleporters[i].destination != 0)
+			debugPrintf(" -> %d\n", kNumberedTeleporters[i].destination);
+		else
+			debugPrintf(" -> nowhere\n");
+	}
+	return true;
+}
+
+bool Debugger::cmdTeleporter(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Usage: teleporter <number>\n");
+		debugPrintf("Recalls working teleporter 1-4 into the cell in front of the player.\n");
+		debugPrintf("Use 'teleporters' to list their current locations.\n");
+		return true;
+	}
+
+	char *end = nullptr;
+	const long number = strtol(argv[1], &end, 10);
+	if (!argv[1][0] || *end != '\0' || number < 1 || number > (long)ARRAYSIZE(kNumberedTeleporters)) {
+		debugPrintf("Invalid teleporter number '%s' (must be 1-4)\n", argv[1]);
+		return true;
+	}
+
+	if (_vm->_gameMode != kModeColony || _vm->_level < 1 || _vm->_level > 7) {
+		debugPrintf("Must be in colony mode to recall a teleporter\n");
+		return true;
+	}
+
+	int level, x, y;
+	const bool relocated = getTeleporterLocation((int)number, level, x, y);
+	if (level != 100 && (level < 1 || level > 7 || x < 0 || x > 31 || y < 0 || y > 31)) {
+		debugPrintf("Teleporter %ld has an invalid location: level %d at (%d, %d)\n",
+			number, level, x, y);
+		return true;
+	}
+
+	const int facingX = _vm->_cost[_vm->_me.ang];
+	const int facingY = _vm->_sint[_vm->_me.ang];
+	const bool horizontal = ABS(facingX) >= ABS(facingY);
+	const int stepX = horizontal ? (facingX >= 0 ? 1 : -1) : 0;
+	const int stepY = horizontal ? 0 : (facingY >= 0 ? 1 : -1);
+	const int targetX = _vm->_me.xindex + stepX;
+	const int targetY = _vm->_me.yindex + stepY;
+	if (targetX < 1 || targetX > 30 || targetY < 1 || targetY > 30) {
+		debugPrintf("Target cell (%d, %d) is out of bounds\n", targetX, targetY);
+		return true;
+	}
+
+	if (level == _vm->_level && x == targetX && y == targetY) {
+		debugPrintf("Teleporter %ld is already in front of the player at (%d, %d)\n",
+			number, targetX, targetY);
+		return false;
+	}
+
+	if (_vm->_robotArray[targetX][targetY] != 0) {
+		debugPrintf("Target cell (%d, %d) is already occupied\n", targetX, targetY);
+		return true;
+	}
+
+	bool blocked = false;
+	if (stepX > 0)
+		blocked = (_vm->_wall[targetX][targetY] & 2) != 0;
+	else if (stepX < 0)
+		blocked = (_vm->_wall[_vm->_me.xindex][_vm->_me.yindex] & 2) != 0;
+	else if (stepY > 0)
+		blocked = (_vm->_wall[targetX][targetY] & 1) != 0;
+	else
+		blocked = (_vm->_wall[_vm->_me.xindex][_vm->_me.yindex] & 1) != 0;
+	if (blocked) {
+		debugPrintf("A wall blocks target cell (%d, %d)\n", targetX, targetY);
+		return true;
+	}
+
+	if (!relocated && _vm->_patches.size() >= 100) {
+		debugPrintf("Cannot recall teleporter %ld: relocation table is full\n", number);
+		return true;
+	}
+
+	int oldObject = -1;
+	if (level == _vm->_level) {
+		for (uint i = 0; i < _vm->_objects.size(); i++) {
+			const Thing &obj = _vm->_objects[i];
+			if (obj.alive && obj.type == kObjTeleport &&
+					obj.where.xindex == x && obj.where.yindex == y) {
+				oldObject = i;
+				break;
+			}
+		}
+	}
+
+	if (oldObject >= 0) {
+		if (_vm->_robotArray[x][y] == oldObject + 1)
+			_vm->_robotArray[x][y] = 0;
+		_vm->_objects[oldObject].alive = 0;
+	}
+
+	const int targetXLoc = (targetX << 8) + 128;
+	const int targetYLoc = (targetY << 8) + 128;
+	if (!_vm->createObject(kObjTeleport, targetXLoc, targetYLoc, 0)) {
+		if (oldObject >= 0) {
+			_vm->_objects[oldObject].alive = 1;
+			_vm->_robotArray[x][y] = oldObject + 1;
+		}
+		debugPrintf("Failed to place teleporter %ld at (%d, %d)\n", number, targetX, targetY);
+		return true;
+	}
+
+	PassPatch from = {};
+	from.level = level;
+	from.xindex = x;
+	from.yindex = y;
+
+	PassPatch to = {};
+	to.level = _vm->_level;
+	to.xindex = targetX;
+	to.yindex = targetY;
+	to.xloc = targetXLoc;
+	to.yloc = targetYLoc;
+	to.ang = 0;
+
+	uint8 mapdata[5] = {6, kObjTeleport - kBaseObject, 0, 0, 0};
+	const int destination = kNumberedTeleporters[number - 1].destination;
+	if (destination != 0) {
+		mapdata[2] = 7;
+		mapdata[3] = kNumberedTeleporters[destination - 1].x;
+		mapdata[4] = kNumberedTeleporters[destination - 1].y;
+	}
+	_vm->newPatch(kObjTeleport, from, to, mapdata);
+
+	if (level == 100 && _vm->_carryType == kObjTeleport) {
+		_vm->_carryType = 0;
+		if (_vm->_fl == 2)
+			_vm->_fl = 1;
+	}
+	_vm->_bumpedObject = 0;
+	_vm->setPlayerCellMarker();
+
+	debugPrintf("Recalled teleporter %ld to level %d at (%d, %d)\n",
+		number, _vm->_level, targetX, targetY);
+	return false;
+}
+
 bool Debugger::cmdTeleport(int argc, const char **argv) {
 	if (argc < 2) {
 		debugPrintf("Usage: teleport <level> [<x> <y>]\n");
@@ -190,6 +413,63 @@ bool Debugger::cmdInfo(int argc, const char **argv) {
 	return true;
 }
 
+bool Debugger::cmdGoals(int argc, const char **argv) {
+	if (argc != 1) {
+		debugPrintf("Usage: goals\n");
+		return true;
+	}
+
+	const int savedCryos = _vm->countSavedCryos();
+	debugPrintf("=== Mission Goals ===\n");
+	debugPrintf("Cryogenic chambers recovered: %d / 6%s\n",
+		savedCryos, savedCryos == 6 ? " (complete)" : "");
+
+	const char *reactorNames[] = {"Ship", "Colony"};
+	for (int i = 0; i < 2; i++) {
+		debugPrintf("%s reactor: %s; power %s (%d)\n",
+			reactorNames[i], reactorStateName(_vm->_coreState[i]),
+			reactorPowerName(_vm->_corePower[i]), _vm->_corePower[i]);
+	}
+
+	if (_vm->_fl == 2 && _vm->_carryType == kObjReactor) {
+		debugPrintf("Reactor core in forklift: yes; power %s (%d)\n",
+			reactorPowerName(_vm->_corePower[2]), _vm->_corePower[2]);
+	} else {
+		debugPrintf("Reactor core in forklift: no\n");
+	}
+	return true;
+}
+
+bool Debugger::cmdWin(int argc, const char **argv) {
+	static const char *const endingDescriptions[] = {
+		"planet destroyed, all 6 cryos recovered",
+		"planet destroyed, some cryos recovered",
+		"planet destroyed, no cryos recovered",
+		"planet spared, all 6 cryos recovered",
+		"planet spared, some cryos recovered",
+		"planet spared, no cryos recovered"
+	};
+
+	if (argc != 2) {
+		debugPrintf("Usage: win <ending>\n");
+		for (uint i = 0; i < ARRAYSIZE(endingDescriptions); i++)
+			debugPrintf("  %u: %s\n", i + 1, endingDescriptions[i]);
+		return true;
+	}
+
+	char *end = nullptr;
+	const long ending = strtol(argv[1], &end, 10);
+	if (!argv[1][0] || *end != '\0' || ending < 1 || ending > (long)ARRAYSIZE(endingDescriptions)) {
+		debugPrintf("Invalid ending number '%s' (must be 1-6)\n", argv[1]);
+		return true;
+	}
+
+	debugPrintf("Playing ending %ld: %s\n", ending, endingDescriptions[ending - 1]);
+	_pendingEnding = (int)ending;
+	detach();
+	return false;
+}
+
 bool Debugger::cmdRobots(int argc, const char **argv) {
 	int count = 0;
 	for (int i = 0; i < (int)_vm->_objects.size(); i++) {
diff --git a/engines/colony/console.h b/engines/colony/console.h
index 8a2b0630d9a..b7cb9af782c 100644
--- a/engines/colony/console.h
+++ b/engines/colony/console.h
@@ -40,9 +40,15 @@ public:
 
 private:
 	ColonyEngine *_vm = nullptr;
+	int _pendingEnding = 0;
+	void postEnter() override;
 	bool cmdTeleport(int argc, const char **argv);
+	bool cmdTeleporter(int argc, const char **argv);
+	bool cmdTeleporters(int argc, const char **argv);
 	bool cmdPos(int argc, const char **argv);
 	bool cmdInfo(int argc, const char **argv);
+	bool cmdGoals(int argc, const char **argv);
+	bool cmdWin(int argc, const char **argv);
 	bool cmdRobots(int argc, const char **argv);
 	bool cmdMap(int argc, const char **argv);
 	bool cmdGive(int argc, const char **argv);
@@ -52,6 +58,7 @@ private:
 	bool cmdColony(int argc, const char **argv);
 	bool cmdForklift(int argc, const char **argv);
 	bool cmdSpawn(int argc, const char **argv);
+	bool getTeleporterLocation(int number, int &level, int &x, int &y) const;
 };
 
 }
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index e50c9161447..b8d54a892f1 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -1458,6 +1458,10 @@ void ColonyEngine::fullOfStars() {
 }
 
 void ColonyEngine::gameOver(bool kill) {
+	gameOver(kill, countSavedCryos());
+}
+
+void ColonyEngine::gameOver(bool kill, int savedCryos) {
 	Common::Rect savedScreenR = _screenR;
 	Common::Rect savedClip = _clip;
 	int savedCenterX = _centerX;
@@ -1473,7 +1477,6 @@ void ColonyEngine::gameOver(bool kill) {
 	CursorMan.setDefaultArrowCursor(true);
 	CursorMan.showMouse(true);
 
-	const int savedCryos = countSavedCryos();
 	int textEntry;
 
 	if (kill)


Commit: d8df792d60051d1329b0dc4bfec6e26a25eb6157
    https://github.com/scummvm/scummvm/commit/d8df792d60051d1329b0dc4bfec6e26a25eb6157
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-22T11:59:36+02:00

Commit Message:
COLONY: adjusted message displaying to match the original sources

Changed paths:
    engines/colony/animation.cpp
    engines/colony/colony.cpp
    engines/colony/colony.h
    engines/colony/interaction.cpp
    engines/colony/intro.cpp
    engines/colony/movement.cpp
    engines/colony/sound.cpp
    engines/colony/sound.h
    engines/colony/ui.cpp


diff --git a/engines/colony/animation.cpp b/engines/colony/animation.cpp
index 02607ade341..fa1cffc3b5e 100644
--- a/engines/colony/animation.cpp
+++ b/engines/colony/animation.cpp
@@ -605,6 +605,7 @@ void ColonyEngine::playAnimation() {
 				needsDraw = true;
 			} else if (event.type == Common::EVENT_LBUTTONDOWN) {
 				const Common::Point pt = eventMouseToLogical(event.mouse);
+				_messageSourceRect = Common::Rect();
 				if (handleColonyCoderClick(pt)) {
 					needsDraw = true;
 				} else if (!_animExitStrip.isEmpty() && _animExitStrip.contains(pt)) {
@@ -617,6 +618,7 @@ void ColonyEngine::playAnimation() {
 					int item = whichSprite(pt);
 					if (item > 0) {
 						handleAnimationClick(item);
+						_messageSourceRect = Common::Rect();
 						needsDraw = true;
 					}
 				}
@@ -692,6 +694,7 @@ void ColonyEngine::playAnimation() {
 				}
 
 				if (item > 0) {
+					_messageSourceRect = Common::Rect();
 					handleAnimationClick(item);
 					needsDraw = true;
 				}
@@ -1390,6 +1393,8 @@ int ColonyEngine::whichSprite(const Common::Point &p) {
 
 		debugC(1, kColonyDebugAnimation, "Sprite %d HIT. type=%d frozen=%d Frame %d, Sprite %d. Box: (%d,%d,%d,%d)",
 			i + 1, ls->type, ls->frozen, cnum, spriteIdx, r.left, r.top, r.right, r.bottom);
+		r.translate(ox, oy);
+		_messageSourceRect = r;
 		return i + 1;
 	}
 
diff --git a/engines/colony/colony.cpp b/engines/colony/colony.cpp
index 4842a06a2aa..7586914bf0b 100644
--- a/engines/colony/colony.cpp
+++ b/engines/colony/colony.cpp
@@ -1357,4 +1357,63 @@ bool ColonyEngine::waitForInput() {
 	return false;
 }
 
+bool ColonyEngine::waitForMessageInput() {
+	// Ignore the input that opened the message.
+	_moveForward = _moveBackward = false;
+	_strafeLeft = _strafeRight = false;
+	_rotateLeft = _rotateRight = false;
+	_sprint = false;
+
+	Common::EventManager *eventMan = _system->getEventManager();
+	auto handleSystemEvent = [&](const Common::Event &event) {
+		if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+			quitGame();
+			return false;
+		}
+		if (event.type == Common::EVENT_SCREEN_CHANGED)
+			_gfx->computeScreenViewport();
+		return true;
+	};
+
+	while (eventMan->getButtonState() && !shouldQuit()) {
+		Common::Event event;
+		while (eventMan->pollEvent(event)) {
+			if (!handleSystemEvent(event))
+				return false;
+		}
+		_system->updateScreen();
+		_system->delayMillis(10);
+	}
+
+	{
+		Common::Event event;
+		while (eventMan->pollEvent(event)) {
+			if (!handleSystemEvent(event))
+				return false;
+		}
+	}
+
+	eventMan->purgeMouseEvents();
+	eventMan->purgeKeyboardEvents();
+
+	while (!shouldQuit()) {
+		Common::Event event;
+		while (eventMan->pollEvent(event)) {
+			if (!handleSystemEvent(event))
+				return false;
+
+			if (event.type == Common::EVENT_LBUTTONDOWN ||
+					event.type == Common::EVENT_RBUTTONDOWN ||
+					event.type == Common::EVENT_KEYDOWN ||
+					event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
+				return true;
+			}
+		}
+		_system->updateScreen();
+		_system->delayMillis(10);
+	}
+
+	return false;
+}
+
 } // End of namespace Colony
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index b159fe44698..a8178fd0114 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -496,6 +496,7 @@ public:
 	bool checkSkipRequested();
 	bool checkClickRequested();
 	bool waitForInput();
+	bool waitForMessageInput();
 	void checkCenter();
 	void fallThroughHole();
 	void playTunnelEffect(bool falling);
@@ -864,6 +865,7 @@ private:
 	bool _animExitInside = false;
 	Common::Rect _animExitStrip;
 	Common::Rect _animExitButton;
+	Common::Rect _messageSourceRect;
 	int _coderPick[4] = {};
 	int _coderCursor = 0;
 	int _coderPressed = -1;
@@ -891,7 +893,7 @@ private:
 	bool makeStars(const Common::Rect &r, int btn);
 	bool makeBlackHole();
 	bool makePlanet();
-	bool timeSquare(const Common::String &str, const Graphics::Font *macFont = nullptr);
+	bool timeSquare(const Common::String &str, const Graphics::Font *macFont = nullptr, bool gameOver = false);
 	bool drawPict(int resID);
 	bool loadAnimation(const Common::String &name);
 	void deleteAnimation();
diff --git a/engines/colony/interaction.cpp b/engines/colony/interaction.cpp
index 212836f6c66..15d7aac0a82 100644
--- a/engines/colony/interaction.cpp
+++ b/engines/colony/interaction.cpp
@@ -82,7 +82,6 @@ void ColonyEngine::interactWithObject(int objNum) {
 				playAnimation();
 			break;
 		default:
-			inform("IT DOES NOT SEEM TO BE WORKING.", true);
 			break;
 		}
 		break;
@@ -97,7 +96,6 @@ void ColonyEngine::interactWithObject(int objNum) {
 				playAnimation();
 			break;
 		default:
-			inform("PROJECTOR OFFLINE", true);
 			break;
 		}
 		break;
@@ -167,16 +165,13 @@ void ColonyEngine::interactWithObject(int objNum) {
 	case kObjToilet:
 	case kObjPToilet:
 		_sound->play(Sound::kToilet);
-		inform("IT'S A TOILET.", true);
 		break;
 	case kObjTub:
 		_sound->play(Sound::kBath);
-		inform("A BATHTUB. NO TIME FOR A SOAK.", true);
 		break;
 
 	case kObjSink:
 		_sound->play(Sound::kSink);
-		inform("A SINK. IT'S DRY.", true);
 		break;
 	case kObjTV:
 		if (_level == 1)
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index b8d54a892f1..dc00baa3688 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -38,7 +38,6 @@
 #include "graphics/macgui/mactext.h"
 #include "graphics/macgui/macwindowmanager.h"
 #include "graphics/managed_surface.h"
-#include "gui/message.h"
 #include "image/pict.h"
 
 #include "colony/colony.h"
@@ -1050,7 +1049,7 @@ bool ColonyEngine::makePlanet() {
 	return false;
 }
 
-bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *macFont) {
+bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *macFont, bool gameOver) {
 	// Original: TimeSquare() in intro.c
 	// Mac and DOS use different presentation here. DOS is a monochrome/gray
 	// warning band with 16-pixel blits and white text; Mac uses the colorful
@@ -1059,13 +1058,18 @@ bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *m
 	_gfx->clear(_gfx->black());
 
 	Graphics::DosFont dosFont;
-	const Graphics::Font *font = macFont ? macFont : (const Graphics::Font *)&dosFont;
+	Graphics::MacFont systemFont(Graphics::kMacFontSystem, 12);
+	const bool macStyle = isMacRenderMode();
+	const Graphics::Font *fallbackMacFont = macStyle && _wm && _wm->_fontMan ?
+		_wm->_fontMan->getFont(systemFont) : nullptr;
+	const Graphics::Font *font = macStyle ? (macFont ? macFont : fallbackMacFont) : nullptr;
+	if (!font)
+		font = &dosFont;
 	int swidth = font->getStringWidth(str);
 
 	int centery = _height / 2 - 10;
 
-	const bool bwMac = (macFont && !isMacColorMode());
-	const bool macStyle = (macFont != nullptr);
+	const bool bwMac = macStyle && !isMacColorMode();
 	const uint32 grayIndex = 160;
 	const uint32 textIndex = 176;
 	const Common::Rect textBand(0, centery + 1, _width, centery + 16);
@@ -1112,7 +1116,7 @@ bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *m
 	// DOS uses 16-pixel blits of a black text box; Mac scrolls smoothly.
 	int targetX = (_width - swidth) / 2;
 	const int startX = macStyle ? _width : (_width + 16);
-	const int stepX = macStyle ? 2 : 16;
+	const int stepX = macStyle ? 1 : 16;
 	const int endX = macStyle ? -swidth : (-swidth - 16);
 	const uint32 scrollDelayMs = macStyle ? 8 : (1000 / 60);
 
@@ -1126,38 +1130,34 @@ bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *m
 		_system->delayMillis(scrollDelayMs);
 	}
 
-	// Phase 2: Klaxon flash — original intro.c lines 312-322.
-	// DOS does 4 full klaxon cycles here; Mac uses the longer 6-flash variant.
+	// Wait for each cue before the next flash.
 	_sound->stop();
 	_gfx->setXorMode(true);
 	const int klaxonCount = macStyle ? 6 : 4;
-	const uint32 dosKlaxonFlashMs = 12 * 1000 / 60;
 	for (int i = 0; i < klaxonCount; i++) {
-		if (checkSkipRequested()) {
-			_gfx->setXorMode(false);
-			return true;
+		while (_sound->isPlaying() && !shouldQuit()) {
+			if (checkSkipRequested()) {
+				_gfx->setXorMode(false);
+				return true;
+			}
+			_system->delayMillis(10);
 		}
+		_sound->stop();
 
-		// InvertRect(&invrt) — XOR the text band
+		_sound->play(macStyle && gameOver ? Sound::kChime : Sound::kKlaxon);
 		_gfx->fillRect(textBand, 0xFFFFFFFF);
 		_gfx->copyToScreen();
-
-		_sound->play(Sound::kKlaxon);
-		if (macStyle) {
-			// Keep the snappier Mac timing.
-			_system->delayMillis(200);
-		} else {
-			// At modern frame rates, waiting for the synthesized klaxon to end
-			// drags these warning cards out too long. Keep a short fixed flash.
-			_system->delayMillis(dosKlaxonFlashMs);
+		if (!_sound->isPlaying())
+			_system->delayMillis(100);
+	}
+	while (_sound->isPlaying() && !shouldQuit()) {
+		if (checkSkipRequested()) {
+			_gfx->setXorMode(false);
+			return true;
 		}
+		_system->delayMillis(10);
 	}
 	_gfx->setXorMode(false);
-	if (macStyle) {
-		// Wait for last klaxon to finish
-		while (_sound->isPlaying() && !shouldQuit())
-			_system->delayMillis(10);
-	}
 	_sound->stop();
 
 	// Phase 3: Mac resumes Mars here; DOS scrolls out silently.
@@ -1299,12 +1299,6 @@ void ColonyEngine::terminateGame(bool blowup) {
 	_gfx->clear(_gfx->black());
 	_gfx->copyToScreen();
 
-	const char *msg[] = {
-		"YOU HAVE BEEN TERMINATED",
-		nullptr
-	};
-	printMessage(msg, true);
-
 	_screenR = savedScreenR;
 	_clip = savedClip;
 	_centerX = savedCenterX;
@@ -1333,28 +1327,71 @@ void ColonyEngine::terminateGame(bool blowup) {
 		return;
 	}
 
+	const char *msg[] = {
+		"   YOU HAVE BEEN TERMINATED!   ",
+		" Type 'n' to start a new game. ",
+		" Type 'l' to load a game.      ",
+		" Type 'q' to quit the game.    ",
+		nullptr
+	};
+	Common::EventManager *eventMan = _system->getEventManager();
+
 	while (!shouldQuit()) {
-		Common::U32StringArray altButtons;
-		altButtons.push_back(_("Load Game"));
-		altButtons.push_back(_("Quit"));
-		GUI::MessageDialog prompt(_("You have been terminated."), _("New Game"), altButtons);
+		printMessage(msg, false);
+		eventMan->purgeKeyboardEvents();
+		int choice = 0;
+		while (!choice && !shouldQuit()) {
+			Common::Event event;
+			while (eventMan->pollEvent(event)) {
+				switch (event.type) {
+				case Common::EVENT_QUIT:
+				case Common::EVENT_RETURN_TO_LAUNCHER:
+					choice = 'q';
+					break;
+				case Common::EVENT_KEYDOWN:
+					if (event.kbd.keycode == Common::KEYCODE_n)
+						choice = 'n';
+					else if (event.kbd.keycode == Common::KEYCODE_l)
+						choice = 'l';
+					else if (event.kbd.keycode == Common::KEYCODE_q)
+						choice = 'q';
+					break;
+				case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
+					// Q is mapped to rotate left.
+					if (event.customType == kActionRotateLeft || event.customType == kActionEscape)
+						choice = 'q';
+					break;
+				case Common::EVENT_SCREEN_CHANGED:
+					_gfx->computeScreenViewport();
+					printMessage(msg, false);
+					break;
+				default:
+					break;
+				}
+			}
+			_system->updateScreen();
+			_system->delayMillis(10);
+		}
 
-		switch (runDialog(prompt)) {
-		case GUI::kMessageOK:
+		switch (choice) {
+		case 'n':
+			inform("New Game!", false);
 			startNewGame();
 			_mouseLocked = savedMouseLocked;
 			updateMouseCapture(true);
 			return;
-		case GUI::kMessageAlt:
+		case 'l':
 			if (loadGameDialog()) {
 				_mouseLocked = savedMouseLocked;
 				updateMouseCapture(true);
 				return;
 			}
 			break;
-		default:
+		case 'q':
 			quitGame();
 			return;
+		default:
+			break;
 		}
 	}
 }
@@ -1510,36 +1547,68 @@ void ColonyEngine::gameOver(bool kill, int savedCryos) {
 	_gfx->copyToScreen();
 	doText(textEntry, 2);
 
-	_gfx->clear(_gfx->black());
-	_gfx->copyToScreen();
-	_sound->play(Sound::kStars4);
-	makeStars(_screenR, 0);
-	_sound->stop();
+	auto playFinalExplosion = [&]() {
+		_gfx->clear(_gfx->black());
+		_gfx->copyToScreen();
+		_sound->play(Sound::kExplode);
+		if (_sound->isPlaying()) {
+			while (_sound->isPlaying() && !shouldQuit()) {
+				_gfx->clear(_gfx->white());
+				_gfx->copyToScreen();
+				_system->delayMillis(50);
+				_gfx->clear(_gfx->black());
+				_gfx->copyToScreen();
+				_system->delayMillis(50);
+			}
+		} else {
+			for (int i = 0; i < 4; i++) {
+				_gfx->clear((i & 1) ? _gfx->black() : _gfx->white());
+				_gfx->copyToScreen();
+				_system->delayMillis(50);
+			}
+		}
+		_sound->stop();
+	};
 
-	_gfx->clear(_gfx->black());
-	_gfx->copyToScreen();
-	timeSquare("...THE END...", nullptr);
+	if (isMacRenderMode()) {
+		_gfx->clear(_gfx->black());
+		_gfx->copyToScreen();
+		_sound->play(Sound::kMars, true);
+		makeStars(_screenR, 0);
 
-	_gfx->clear(_gfx->black());
-	_gfx->copyToScreen();
-	_sound->play(Sound::kExplode);
-	if (_sound->isPlaying()) {
-		while (_sound->isPlaying() && !shouldQuit()) {
-			_gfx->clear(_gfx->white());
-			_gfx->copyToScreen();
-			_system->delayMillis(50);
-			_gfx->clear(_gfx->black());
-			_gfx->copyToScreen();
-			_system->delayMillis(50);
+		Graphics::MacFONTFont *macFont = nullptr;
+		if (_resMan) {
+			const uint16 fontResID = 24332; // FOND 190, 12pt
+			Common::SeekableReadStream *fontStream = _resMan->getResource(MKTAG('N', 'F', 'N', 'T'), fontResID);
+			if (!fontStream)
+				fontStream = _resMan->getResource(MKTAG('F', 'O', 'N', 'T'), fontResID);
+			if (fontStream) {
+				macFont = new Graphics::MacFONTFont();
+				if (!macFont->loadFont(*fontStream)) {
+					delete macFont;
+					macFont = nullptr;
+				}
+				delete fontStream;
+			}
 		}
+		timeSquare("...THE END...", macFont, true);
+		delete macFont;
+
+		_gfx->clear(_gfx->black());
+		_gfx->copyToScreen();
+		makeStars(_screenR, 0);
+		_sound->stop();
+		playFinalExplosion();
 	} else {
-		for (int i = 0; i < 4; i++) {
-			_gfx->clear((i & 1) ? _gfx->black() : _gfx->white());
-			_gfx->copyToScreen();
-			_system->delayMillis(50);
-		}
+		// DOS uses Inform here, not TimeSquare.
+		_gfx->clear(_gfx->black());
+		_gfx->copyToScreen();
+		_sound->play(Sound::kStars4);
+		makeStars(_screenR, 0);
+		_sound->stop();
+		playFinalExplosion();
+		inform("THE END", true);
 	}
-	_sound->stop();
 
 	_screenR = savedScreenR;
 	_clip = savedClip;
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index 297ab2ad35b..3df6164f05f 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -912,10 +912,8 @@ int ColonyEngine::tryPassThroughFeature(int fromX, int fromY, int direction, Loc
 	case kWallFeatureElevator: {
 		if (pobject != &_me)
 			return 0;
-		if (_corePower[1] == 0) {
-			inform("ELEVATOR HAS NO POWER.", true);
+		if (_corePower[1] == 0)
 			return 0;
-		}
 
 		// DOS DoElevator: play elevator animation with floor selection
 		if (!loadAnimation("elev"))
diff --git a/engines/colony/sound.cpp b/engines/colony/sound.cpp
index ae342bc7530..82d232786e0 100644
--- a/engines/colony/sound.cpp
+++ b/engines/colony/sound.cpp
@@ -194,6 +194,7 @@ void Sound::playPCSpeaker(int soundID) {
 		}
 		break;
 	case kChime:
+	case kDiDit:
 		queueTick(4649, 7);
 		queueTick(3690, 7);
 		queueTick(3103, 7);
@@ -359,6 +360,7 @@ bool Sound::playMacSound(int soundID, bool loop) {
 	case kPShot: resID = 27539; break;  // PLANETSHOT
 	case kTest: resID = 25795; break;
 	case kDit: resID = 1516; break;
+	case kDiDit: resID = 4274; break;
 	case kSink: resID = 2920; break;
 	case kClatter: resID = 11208; break;
 	case kStop: resID = 29382; break;   // FULLSTOP
diff --git a/engines/colony/sound.h b/engines/colony/sound.h
index a83212f3238..60e4ec147f4 100644
--- a/engines/colony/sound.h
+++ b/engines/colony/sound.h
@@ -62,6 +62,7 @@ public:
 		kPShot,
 		kTest,
 		kDit,
+		kDiDit,
 		kSink,
 		kClatter,
 		kStop,
diff --git a/engines/colony/ui.cpp b/engines/colony/ui.cpp
index c8729abbd50..28b718ec27a 100644
--- a/engines/colony/ui.cpp
+++ b/engines/colony/ui.cpp
@@ -31,6 +31,7 @@
 #include "common/macresman.h"
 #include "common/system.h"
 #include "common/util.h"
+#include "graphics/cursorman.h"
 #include "graphics/fontman.h"
 #include "graphics/fonts/dosfont.h"
 #include "graphics/macgui/macfontmanager.h"
@@ -45,11 +46,72 @@
 
 namespace Colony {
 
-bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
+enum MacTextPopupStyle {
+	kMacTextWindow,
+	kMacInformWindow
+};
+
+static Graphics::ManagedSurface *captureMessageBackground(Renderer *gfx, int width, int height) {
+	if (!gfx)
+		return nullptr;
+
+	Graphics::Surface *screenshot = gfx->getScreenshot();
+	if (!screenshot)
+		return nullptr;
+
+	Graphics::ManagedSurface *saved = new Graphics::ManagedSurface();
+	saved->create(width, height, screenshot->format);
+	saved->blitFrom(*screenshot, Common::Rect(screenshot->w, screenshot->h), Common::Rect(width, height));
+	screenshot->free();
+	delete screenshot;
+	return saved;
+}
+
+static void restoreMessageBackground(Renderer *gfx, Graphics::ManagedSurface *saved) {
+	if (!saved)
+		return;
+	if (gfx) {
+		gfx->drawSurface(&saved->rawSurface(), 0, 0);
+		gfx->copyToScreen();
+	}
+	saved->free();
+	delete saved;
+}
+
+static void animateMacZoom(Renderer *gfx, OSystem *system, const Common::Rect &from, const Common::Rect &to) {
+	if (!gfx || !system || from.isEmpty() || to.isEmpty())
+		return;
+
+	const bool cursorWasVisible = CursorMan.isVisible();
+	CursorMan.showMouse(false);
+	gfx->setXorMode(true);
+	const int steps = 8;
+	for (int i = 0; i <= steps; ++i) {
+		Common::Rect r;
+		r.left = (from.left * (steps - i) + to.left * i) / steps;
+		r.top = (from.top * (steps - i) + to.top * i) / steps;
+		r.right = (from.right * (steps - i) + to.right * i) / steps;
+		r.bottom = (from.bottom * (steps - i) + to.bottom * i) / steps;
+		gfx->drawRect(r, 0xFFFFFFFF);
+		gfx->copyToScreen();
+		system->delayMillis(12);
+		gfx->drawRect(r, 0xFFFFFFFF);
+	}
+	gfx->setXorMode(false);
+	gfx->copyToScreen();
+	CursorMan.showMouse(cursorWasVisible);
+}
+
+static bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 		int screenWidth, int screenHeight, int centerX, int centerY,
-		const Common::Array<Common::String> &lines, Graphics::TextAlign align, bool macColor) {
+		const Common::Array<Common::String> &lines, Graphics::TextAlign align, bool macColor,
+		int visibleLineCount = -1, Common::Rect *popupBounds = nullptr,
+		bool measureOnly = false, MacTextPopupStyle style = kMacTextWindow) {
 	if (!gfx || lines.empty())
 		return false;
+	uint visibleLines = lines.size();
+	if (visibleLineCount >= 0 && (uint)visibleLineCount < visibleLines)
+		visibleLines = visibleLineCount;
 
 	Graphics::MacFont systemFont(Graphics::kMacFontSystem, 12);
 	const Graphics::Font *font = (wm && wm->_fontMan) ? wm->_fontMan->getFont(systemFont) : nullptr;
@@ -65,15 +127,17 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 		textWidth = MAX<int>(textWidth, font->getStringWidth(lines[i]));
 
 	const int fontHeight = MAX<int>(1, font->getFontHeight());
-	const int fontLeading = MAX<int>(0, font->getFontLeading());
-	const int topPad = 8;
-	const int bottomPad = 8;
-	const int sidePad = 12;
-	const int lineGap = MAX<int>(2, fontLeading);
-	const int lineStep = fontHeight + lineGap;
-	int popupWidth = CLIP<int>(textWidth + sidePad * 2, 96, MAX<int>(96, screenWidth - 16));
-	int popupHeight = topPad + bottomPad + fontHeight +
-		MAX<int>(0, (int)lines.size() - 1) * lineStep;
+	const int sidePad = 8;
+	const int lineStep = align == Graphics::kTextAlignCenter ? 18 : 20;
+	const int contentWidth = style == kMacInformWindow ? 370 : textWidth + 16;
+	const int contentHeight = style == kMacInformWindow ? 72 : 4 + ((int)lines.size() + 1) * 18;
+	Common::Rect contentRect;
+	if (style == kMacInformWindow)
+		contentRect = Common::Rect(56, 60, 56 + contentWidth, 60 + contentHeight);
+	else
+		contentRect = Common::Rect(centerX - contentWidth / 2, centerY - contentHeight / 2,
+			centerX - contentWidth / 2 + contentWidth, centerY - contentHeight / 2 + contentHeight);
+
 	Common::Rect bounds(8, 24, screenWidth - 8, screenHeight - 8);
 	if (wm) {
 		Graphics::MacWindowBorder border;
@@ -81,14 +145,12 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 		border.setBorderType(Graphics::kWindowWindow);
 		if (border.hasBorder(Graphics::kWindowBorderActive) && border.hasOffsets()) {
 			const Graphics::BorderOffsets &offsets = border.getOffset();
-			popupWidth = MAX<int>(border.getMinWidth(Graphics::kWindowBorderActive),
-				textWidth + sidePad * 2 + offsets.left + offsets.right);
-			popupHeight = MAX<int>(border.getMinHeight(Graphics::kWindowBorderActive),
-				topPad + bottomPad + fontHeight + MAX<int>(0, (int)lines.size() - 1) * lineStep +
-				offsets.top + offsets.bottom);
-
-			Common::Rect r(centerX - popupWidth / 2, centerY - popupHeight / 2,
-				centerX - popupWidth / 2 + popupWidth, centerY - popupHeight / 2 + popupHeight);
+			const int popupWidth = MAX<int>(border.getMinWidth(Graphics::kWindowBorderActive),
+				contentWidth + offsets.left + offsets.right);
+			const int popupHeight = MAX<int>(border.getMinHeight(Graphics::kWindowBorderActive),
+				contentHeight + offsets.top + offsets.bottom);
+			Common::Rect r(contentRect.left - offsets.left, contentRect.top - offsets.top,
+				contentRect.left - offsets.left + popupWidth, contentRect.top - offsets.top + popupHeight);
 			if (r.left < bounds.left)
 				r.translate(bounds.left - r.left, 0);
 			if (r.right > bounds.right)
@@ -97,6 +159,10 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 				r.translate(0, bounds.top - r.top);
 			if (r.bottom > bounds.bottom)
 				r.translate(0, bounds.bottom - r.bottom);
+			if (popupBounds)
+				*popupBounds = r;
+			if (measureOnly)
+				return true;
 
 			Graphics::ManagedSurface popup;
 			popup.create(popupWidth, popupHeight, wm->_pixelformat);
@@ -108,9 +174,10 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 			border.blitBorderInto(popup, Graphics::kWindowBorderActive);
 
 			const int textX = inner.left + sidePad;
-			const int textY = inner.top + topPad;
+			const int textY = style == kMacInformWindow ?
+				inner.top + (inner.height() - fontHeight) / 2 : inner.top + MAX<int>(0, 14 - fontHeight);
 			const int textW = MAX<int>(1, inner.width() - sidePad * 2);
-			for (uint i = 0; i < lines.size(); ++i)
+			for (uint i = 0; i < visibleLines; ++i)
 				font->drawString(&popup, lines[i], textX, textY + (int)i * lineStep, textW, wm->_colorBlack, align);
 
 			gfx->drawSurface(&popup.rawSurface(), r.left, r.top);
@@ -120,8 +187,7 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 		}
 	}
 
-	Common::Rect r(centerX - popupWidth / 2, centerY - popupHeight / 2,
-		centerX - popupWidth / 2 + popupWidth, centerY - popupHeight / 2 + popupHeight);
+	Common::Rect r = contentRect;
 	if (r.left < bounds.left)
 		r.translate(bounds.left - r.left, 0);
 	if (r.right > bounds.right)
@@ -130,6 +196,10 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 		r.translate(0, bounds.top - r.top);
 	if (r.bottom > bounds.bottom)
 		r.translate(0, bounds.bottom - r.bottom);
+	if (popupBounds)
+		*popupBounds = r;
+	if (measureOnly)
+		return true;
 
 	const uint32 colBlack = macColor ? packRGB(0, 0, 0) : 0;
 	const uint32 colWhite = macColor ? packRGB(255, 255, 255) : 15;
@@ -148,8 +218,9 @@ bool drawMacTextPopup(Graphics::MacWindowManager *wm, Renderer *gfx,
 	const int textLeft = r.left + sidePad;
 	const int textRight = r.right - sidePad;
 	const int textCenter = (textLeft + textRight) / 2;
-	const int startY = r.top + topPad;
-	for (uint i = 0; i < lines.size(); ++i) {
+	const int startY = style == kMacInformWindow ?
+		r.top + (r.height() - fontHeight) / 2 : r.top + MAX<int>(0, 14 - fontHeight);
+	for (uint i = 0; i < visibleLines; ++i) {
 		const int y = startY + (int)i * lineStep;
 		if (align == Graphics::kTextAlignCenter)
 			gfx->drawString(font, lines[i], textCenter, y, colBlack, Graphics::kTextAlignCenter);
@@ -1415,6 +1486,22 @@ void ColonyEngine::drawCrosshair() {
 }
 
 void ColonyEngine::inform(const char *text, bool hold) {
+	if (isMacRenderMode()) {
+		Common::Array<Common::String> lines;
+		lines.push_back(text);
+		Graphics::ManagedSurface *background = hold ? captureMessageBackground(_gfx, _width, _height) : nullptr;
+		if (drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY, lines,
+				Graphics::kTextAlignCenter, isMacColorMode(), -1, nullptr, false, kMacInformWindow)) {
+			if (hold) {
+				waitForMessageInput();
+				restoreMessageBackground(_gfx, background);
+			}
+			return;
+		}
+		restoreMessageBackground(_gfx, background);
+		background = nullptr;
+	}
+
 	const char *msg[3];
 	msg[0] = text;
 	msg[1] = hold ? "-Press Any Key to Continue-" : nullptr;
@@ -1436,21 +1523,21 @@ void ColonyEngine::printMessage(const char *text[], bool hold) {
 		numLines++;
 	}
 
+	Graphics::ManagedSurface *background = hold ? captureMessageBackground(_gfx, _width, _height) : nullptr;
 	if (isMacRenderMode() && drawMacTextPopup(_wm, _gfx,
 			_width, _height, _centerX, _centerY, lines, Graphics::kTextAlignCenter, isMacColorMode())) {
-		if (hold)
-			waitForInput();
+		if (hold) {
+			waitForMessageInput();
+			restoreMessageBackground(_gfx, background);
+		}
 		return;
 	}
 
-	int pxPerInchX = 72;
-	int pxPerInchY = 72;
-
 	Common::Rect rr;
-	rr.top = _centerY - (numLines + 1) * (pxPerInchY / 4);
-	rr.bottom = _centerY + (numLines + 1) * (pxPerInchY / 4);
-	rr.left = _centerX - width / 2 - (pxPerInchX / 2);
-	rr.right = _centerX + width / 2 + (pxPerInchX / 2);
+	rr.top = _centerY - (numLines + 1) * _pQy;
+	rr.bottom = _centerY + (numLines + 1) * _pQy;
+	rr.left = _centerX - width / 2 - 2 * _pQx;
+	rr.right = _centerX + width / 2 + 2 * _pQx;
 
 	_gfx->fillDitherRect(_screenR, 0, 15);
 	makeMessageRect(rr);
@@ -1458,8 +1545,8 @@ void ColonyEngine::printMessage(const char *text[], bool hold) {
 	int start;
 	int step;
 	if (numLines > 1) {
-		start = rr.top + (pxPerInchY / 4) * 2;
-		step = (rr.height() - (pxPerInchY / 4) * 4) / (numLines - 1);
+		start = rr.top + _pQy * 2;
+		step = (rr.height() - _pQy * 4) / (numLines - 1);
 	} else {
 		start = (rr.top + rr.bottom) / 2;
 		step = 0;
@@ -1471,8 +1558,10 @@ void ColonyEngine::printMessage(const char *text[], bool hold) {
 
 	_gfx->copyToScreen();
 
-	if (hold)
-		waitForInput();
+	if (hold) {
+		waitForMessageInput();
+		restoreMessageBackground(_gfx, background);
+	}
 }
 
 void ColonyEngine::makeMessageRect(Common::Rect &rr) {
@@ -1484,6 +1573,11 @@ void ColonyEngine::makeMessageRect(Common::Rect &rr) {
 }
 
 void ColonyEngine::doText(int entry, int center) {
+	Common::Rect messageSource = _messageSourceRect;
+	_messageSourceRect = Common::Rect();
+	if (messageSource.isEmpty())
+		messageSource = _screenR;
+
 	Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(Common::Path("T.DAT"));
 	if (!file)
 		file = Common::MacResManager::openFileOrDataFork(Common::Path("Tdata"));
@@ -1502,7 +1596,7 @@ void ColonyEngine::doText(int entry, int center) {
 	file->seek(4 + entry * 8);
 	uint32 offset = file->readUint32BE();
 	uint16 ch = file->readUint16BE();
-	file->readUint16BE(); // lines (unused)
+	uint16 textLineCount = file->readUint16BE();
 
 	if (ch == 0) {
 		delete file;
@@ -1525,14 +1619,96 @@ void ColonyEngine::doText(int entry, int center) {
 	int start = 0;
 	for (int i = 0; i < ch; i++) {
 		if (p[i] == '\r' || p[i] == '\n') {
-			p[i] = 0;
-			if (p[start])
-				lineArray.push_back(&p[start]);
+			lineArray.push_back(Common::String(&p[start], i - start));
+			if (p[i] == '\r' && i + 1 < ch && p[i + 1] == '\n')
+				i++;
 			start = i + 1;
 		}
 	}
-	if (start < ch && p[start])
-		lineArray.push_back(&p[start]);
+	if (start < ch)
+		lineArray.push_back(Common::String(&p[start], ch - start));
+
+	// Preserve indexed blank lines without adding a trailing empty field.
+	if (textLineCount > 0) {
+		lineArray.resize(textLineCount);
+	}
+
+	Graphics::ManagedSurface *background = captureMessageBackground(_gfx, _width, _height);
+	auto restoreBackground = [&]() {
+		if (background) {
+			restoreMessageBackground(_gfx, background);
+			background = nullptr;
+		} else {
+			_gfx->fillRect(_screenR, 0);
+			_gfx->copyToScreen();
+		}
+	};
+
+	auto waitForLineSound = [&]() {
+		while (_sound->isPlaying() && !shouldQuit()) {
+			Common::Event event;
+			while (_system->getEventManager()->pollEvent(event)) {
+				switch (event.type) {
+				case Common::EVENT_QUIT:
+				case Common::EVENT_RETURN_TO_LAUNCHER:
+					quitGame();
+					return false;
+				case Common::EVENT_SCREEN_CHANGED:
+					_gfx->computeScreenViewport();
+					break;
+				default:
+					break;
+				}
+			}
+			_system->updateScreen();
+			_system->delayMillis(10);
+		}
+		return !shouldQuit();
+	};
+
+	if (isMacRenderMode()) {
+		const Graphics::TextAlign align = center == 1 ?
+			Graphics::kTextAlignCenter : Graphics::kTextAlignLeft;
+		Common::Rect popupBounds;
+
+		if (drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY,
+				lineArray, align, isMacColorMode(), 0, &popupBounds, true)) {
+			animateMacZoom(_gfx, _system, messageSource, popupBounds);
+
+			if (center == 2) {
+				// PlayDiDit waits for the previous cue before each line.
+				drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY,
+					lineArray, align, isMacColorMode(), 0);
+				for (uint i = 0; i < lineArray.size(); ++i) {
+					if (!waitForLineSound()) {
+						restoreBackground();
+						delete[] page;
+						return;
+					}
+					_sound->play(Sound::kDiDit);
+					drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY,
+						lineArray, align, isMacColorMode(), i + 1);
+				}
+			} else {
+				drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY,
+					lineArray, align, isMacColorMode());
+			}
+
+			waitForMessageInput();
+			restoreBackground();
+			if (!shouldQuit())
+				animateMacZoom(_gfx, _system, popupBounds, messageSource);
+			delete[] page;
+			return;
+		}
+	}
+
+	const bool cursorWasVisible = CursorMan.isVisible();
+	CursorMan.showMouse(false);
+	auto finishDosMessage = [&]() {
+		restoreBackground();
+		CursorMan.showMouse(cursorWasVisible);
+	};
 
 	Graphics::DosFont font;
 	int width = 0;
@@ -1542,30 +1718,16 @@ void ColonyEngine::doText(int entry, int center) {
 			width = w;
 	}
 	const char *kpress = "-Press Any Key to Continue-";
-	const char *kmore = "-More-";
 	int kw = font.getStringWidth(kpress);
 	if (kw > width)
 		width = kw;
 	width += 12;
 
 	int lineheight = 14;
-	int maxlines = (_screenR.height() / lineheight) - 2;
+	int maxlines = MAX(1, (_screenR.height() / lineheight) - 1);
 	if (maxlines > (int)lineArray.size())
 		maxlines = lineArray.size();
 
-	if (isMacRenderMode()) {
-		Common::Array<Common::String> popupLines;
-		for (int i = 0; i < maxlines; ++i)
-			popupLines.push_back(lineArray[i]);
-		popupLines.push_back((int)lineArray.size() > maxlines ? kmore : kpress);
-		if (drawMacTextPopup(_wm, _gfx, _width, _height, _centerX, _centerY, popupLines,
-				center == 1 ? Graphics::kTextAlignCenter : Graphics::kTextAlignLeft, isMacColorMode())) {
-			waitForInput();
-			delete[] page;
-			return;
-		}
-	}
-
 	// DOS DOTEXT.C: r positioned at (cX ± wdth, cY ± ((maxlines+1)*7 + 4))
 	// then offset by (+3,+3) for shadow. 3 nested FrameRects shrinking by 1.
 	const int halfH = ((maxlines + 1) * (lineheight / 2)) + 4;
@@ -1585,13 +1747,20 @@ void ColonyEngine::doText(int entry, int center) {
 	}
 	_gfx->fillRect(r, 15);
 	_gfx->drawRect(r, 0);
+	if (center == 2)
+		_gfx->copyToScreen();
 
 	// Draw first page of text
 	for (int i = 0; i < maxlines; i++) {
 		_gfx->drawString(&font, lineArray[i], r.left + 3, r.top + 4 + i * lineheight, 0);
 		if (center == 2) {
+			_gfx->copyToScreen();
 			_sound->play(Sound::kDit);
-			_system->delayMillis(20);
+			if (!waitForLineSound()) {
+				finishDosMessage();
+				delete[] page;
+				return;
+			}
 		}
 	}
 
@@ -1600,28 +1769,44 @@ void ColonyEngine::doText(int entry, int center) {
 	_gfx->drawString(&font, hasMore ? "-Press Any Key For More...-" : kpress,
 		(r.left + r.right) / 2, r.top + 6 + maxlines * lineheight, 0, Graphics::kTextAlignCenter);
 	_gfx->copyToScreen();
-	waitForInput();
+	if (!waitForMessageInput()) {
+		finishDosMessage();
+		delete[] page;
+		return;
+	}
 
 	// Second page: if text was truncated, show remainder
 	// DOS DOTEXT.C: starts from maxlines-1 (repeats last line of page 1 for context)
 	if (hasMore) {
 		_gfx->fillRect(r, 15);
 		_gfx->drawRect(r, 0);
+		if (center == 2)
+			_gfx->copyToScreen();
 		int pageStart = maxlines - 1;
 		for (int i = pageStart; i < (int)lineArray.size() && (i - pageStart) < maxlines; i++) {
 			_gfx->drawString(&font, lineArray[i], r.left + 3,
 				r.top + 6 + (1 + i - pageStart) * lineheight, 0);
 			if (center == 2) {
+				_gfx->copyToScreen();
 				_sound->play(Sound::kDit);
-				_system->delayMillis(20);
+				if (!waitForLineSound()) {
+					finishDosMessage();
+					delete[] page;
+					return;
+				}
 			}
 		}
 		_gfx->drawString(&font, kpress,
 			(r.left + r.right) / 2, r.top + 6 + maxlines * lineheight, 0, Graphics::kTextAlignCenter);
 		_gfx->copyToScreen();
-		waitForInput();
+		if (!waitForMessageInput()) {
+			finishDosMessage();
+			delete[] page;
+			return;
+		}
 	}
 
+	finishDosMessage();
 	delete[] page;
 }
 


Commit: cff4e56a26e74746e9fc1e5c0f634bfe31584a95
    https://github.com/scummvm/scummvm/commit/cff4e56a26e74746e9fc1e5c0f634bfe31584a95
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-08-22T11:59:36+02:00

Commit Message:
SCUMM: RA: manually initialize _vm->_actionMap

Changed paths:
    engines/scumm/insane/rebel1/rebel.cpp
    engines/scumm/insane/rebel2/rebel.cpp


diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index be44fd47b57..3f042b3b229 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -462,6 +462,9 @@ void InsaneRebel1::loadLocalizedUiStrings() {
 
 InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
 	Insane::_vm = scumm;
+	// Rebel Assault skips ScummEngine::resetScumm(), which normally clears this state.
+	for (int i = 0; i < kScummActionCount; i++)
+		_vm->_actionMap[i] = false;
 
 	_screenWidth = 384;
 	_screenHeight = 242;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index f7d3b1cb10f..d449a8fd3da 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -161,6 +161,9 @@ bool InsaneRebel2::isSkippableVideoState() const {
 
 InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 	_vm = scumm;
+	// Rebel Assault II skips ScummEngine::resetScumm(), which normally clears this state.
+	for (int i = 0; i < kScummActionCount; i++)
+		_vm->_actionMap[i] = false;
 
 	_smush_roadrashRip = nullptr;
 	_smush_roadrsh2Rip = nullptr;




More information about the Scummvm-git-logs mailing list