[Scummvm-git-logs] scummvm branch-3-0 -> 14f7b8320e02fa1c8ed6d1f9a55e01c7d78be02f

sluicebox noreply at scummvm.org
Mon Dec 8 19:28:45 UTC 2025


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

Summary:
c8399b8f69 PRIVATE: Combine cursor tables
7644b84aa2 PRIVATE: Reduce global usage
2bc56a6f62 PRIVATE: Remove `inBox` method
dc48c48147 PRIVATE: Rename debug channel to match enum
acf3859a87 PRIVATE: Reduce string creation in `restartGame()`
5b5013c193 PRIVATE: Implement timer in main thread
8ff93cc362 PRIVATE: Implement skipping timer
db5d1f7bbf PRIVATE: Update PhoneClip implementation
0df1ed7e55 PRIVATE: Reduce global usage
14f7b8320e PRIVATE: Fix phone bug when loading game


Commit: c8399b8f69fc00abd9942598b232fd9583c4b1cc
    https://github.com/scummvm/scummvm/commit/c8399b8f69fc00abd9942598b232fd9583c4b1cc
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:30-08:00

Commit Message:
PRIVATE: Combine cursor tables

Changed paths:
    engines/private/cursors.cpp


diff --git a/engines/private/cursors.cpp b/engines/private/cursors.cpp
index 7d739d2a407..93f80ae26cb 100644
--- a/engines/private/cursors.cpp
+++ b/engines/private/cursors.cpp
@@ -41,25 +41,25 @@ namespace Private {
 struct CursorEntry {
 	const char *name;
  	const char *aname;
-	uint id;
-	uint japaneseId;
+	uint windowsId;
+	uint japaneseWindowsId;
+	uint macId;
 };
 
 void PrivateEngine::loadCursors() {
+	const CursorEntry cursorIDReference[] = {
+		{ "kTurnLeft",  "k1", 23, 17, 133 },
+		{ "kTurnRight", "k2", 9,  3,  132 },
+		{ "kZoomIn",    "k3", 17, 11, 138 },
+		{ "kZoomOut",   "k4", 11, 5,  135 },
+		{ "kExit",      "k5", 7,  1,  130 },
+		{ "kPhone",     "k6", 25, 19, 141 },
+		{ "kInventory", "k7", 19, 13, 139 }
+	};
+
 	_defaultCursor = Graphics::makeDefaultWinCursor();
 
 	if (_platform == Common::kPlatformWindows) {
-		const CursorEntry cursorIDReference[] = {
-			{ "kTurnLeft",  "k1", 23, 17 },
-			{ "kTurnRight", "k2", 9,  3  },
-			{ "kZoomIn",    "k3", 17, 11 },
-			{ "kZoomOut",   "k4", 11, 5  },
-			{ "kExit",      "k5", 7,  1  },
-			{ "kPhone",     "k6", 25, 19 },
-			{ "kInventory", "k7", 19, 13 },
-			{ nullptr, nullptr,   0,  0  }
-		};
-
 		Common::WinResources *exe = nullptr;
 		Common::SeekableReadStream *exeStream = nullptr;
 		Common::ArchiveMemberList members;
@@ -97,34 +97,22 @@ void PrivateEngine::loadCursors() {
 			_cursors[i].winCursorGroup = Graphics::WinCursorGroup::createCursorGroup(exe, cursorIDs[i]);
 			_cursors[i].cursor = _cursors[i].winCursorGroup->cursors[0].cursor;
 
-			const CursorEntry *entry = cursorIDReference;
-			while (entry->name != nullptr) {
-				uint entryId = (_language == Common::JA_JPN) ? entry->japaneseId : entry->id;
+			for (uint j = 0; j < ARRAYSIZE(cursorIDReference); j++) {
+				const CursorEntry &entry = cursorIDReference[j];
+
+				uint entryId = (_language == Common::JA_JPN) ? entry.japaneseWindowsId : entry.windowsId;
 				if (entryId == _cursors[i].winCursorGroup->cursors[0].id.getID()) {
-					_cursors[i].name = entry->name;
-					_cursors[i].aname = entry->aname;
+					_cursors[i].name = entry.name;
+					_cursors[i].aname = entry.aname;
 					break;
 				}
-				entry++;
 			}
 		}
 
 		delete exe;
 		delete exeStream;
 	} else {
-		const CursorEntry cursorIDReference[] = {
-			{ "kTurnLeft",  "k1", 133, 0 },
-			{ "kTurnRight", "k2", 132, 0 },
-			{ "kZoomIn",    "k3", 138, 0 },
-			{ "kZoomOut",   "k4", 135, 0 },
-			{ "kExit",      "k5", 130, 0 },
-			{ "kPhone",     "k6", 141, 0 },
-			{ "kInventory", "k7", 139, 0 },
-			{ nullptr, nullptr,   0,   0 }
-		};
-
 		Common::MacResManager resMan;
-
 		const char *executableFilePath = isDemo() ? "SUPPORT/Private Eye Demo" : "SUPPORT/Private Eye";
 		const char *executableInstallerPath = isDemo() ? "Private Eye Demo" : "Private Eye";
 		Common::ScopedPtr<Common::Archive> macInstaller(loadMacInstaller());
@@ -139,14 +127,14 @@ void PrivateEngine::loadCursors() {
 				_cursors[i].cursor = cursor;
 				_cursors[i].winCursorGroup = nullptr;
 
-				const CursorEntry *entry = cursorIDReference;
-				while (entry->name != nullptr) {
-					if (entry->id == cursorResIDs[i]) {
-						_cursors[i].name = entry->name;
-						_cursors[i].aname = entry->aname;
+				for (uint j = 0; j < ARRAYSIZE(cursorIDReference); j++) {
+					const CursorEntry &entry = cursorIDReference[j];
+
+					if (entry.macId == cursorResIDs[i]) {
+						_cursors[i].name = entry.name;
+						_cursors[i].aname = entry.aname;
 						break;
 					}
-					entry++;
 				}
 			}
 		}


Commit: 7644b84aa26539b9ee7624a64c0984f66fbbe72d
    https://github.com/scummvm/scummvm/commit/7644b84aa26539b9ee7624a64c0984f66fbbe72d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:30-08:00

Commit Message:
PRIVATE: Reduce global usage

Changed paths:
    engines/private/private.cpp


diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index bb1f6b51520..7980c67cefc 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -356,8 +356,8 @@ Common::Error PrivateEngine::run() {
 		bool mouseMoved = false;
 		checkPhoneCall();
 
-		while (g_system->getEventManager()->pollEvent(event)) {
-			mousePos = g_system->getEventManager()->getMousePos();
+		while (_system->getEventManager()->pollEvent(event)) {
+			mousePos = _system->getEventManager()->getMousePos();
 			// Events
 			switch (event.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
@@ -442,15 +442,15 @@ Common::Error PrivateEngine::run() {
 				if (_subtitles != nullptr) {
 					delete _subtitles;
 					_subtitles = nullptr;
-					g_system->hideOverlay();
+					_system->hideOverlay();
 				}
 				_currentMovie = "";
 			} else if (!_videoDecoder->needsUpdate() && mouseMoved) {
-				g_system->updateScreen();
+				_system->updateScreen();
 			} else if (_videoDecoder->needsUpdate()) {
 				drawScreen();
 			}
-			g_system->delayMillis(5); // Yield to the system
+			_system->delayMillis(5); // Yield to the system
 			continue;
 		}
 
@@ -479,15 +479,15 @@ Common::Error PrivateEngine::run() {
 			}
 		}
 
-		g_system->updateScreen();
-		g_system->delayMillis(10);
+		_system->updateScreen();
+		_system->delayMillis(10);
 		if (_subtitles != nullptr) {
 			if (_mixer->isSoundHandleActive(_fgSoundHandle)) {
 				_subtitles->drawSubtitle(_mixer->getElapsedTime(_fgSoundHandle).msecs(), false, _sfxSubtitles);
 			} else {
 				delete _subtitles;
 				_subtitles = nullptr;
-				g_system->hideOverlay();
+				_system->hideOverlay();
 			}
 		}
 	}
@@ -497,9 +497,9 @@ Common::Error PrivateEngine::run() {
 
 void PrivateEngine::ignoreEvents() {
 	Common::Event event;
-	g_system->getEventManager()->pollEvent(event);
-	g_system->updateScreen();
-	g_system->delayMillis(10);
+	_system->getEventManager()->pollEvent(event);
+	_system->updateScreen();
+	_system->delayMillis(10);
 }
 
 void PrivateEngine::initFuncs() {
@@ -1906,8 +1906,8 @@ void PrivateEngine::adjustSubtitleSize() {
 		const int MIN_FONT_SIZE = 8;
 		const float BASE_FONT_SIZE_PERCENT = 0.023f;  // ~50px at 2160p
 
-		int16 h = g_system->getOverlayHeight();
-		int16 w = g_system->getOverlayWidth();
+		int16 h = _system->getOverlayHeight();
+		int16 w = _system->getOverlayWidth();
 
 		int bottomMargin = int(h * BOTTOM_MARGIN_PERCENT);
 
@@ -1959,7 +1959,7 @@ void PrivateEngine::loadSubtitles(const Common::Path &path) {
 	if (_subtitles != nullptr) {
 		delete _subtitles;
 		_subtitles = nullptr;
-		g_system->hideOverlay();
+		_system->hideOverlay();
 	}
 
 	_subtitles = new Video::Subtitles();
@@ -1970,8 +1970,8 @@ void PrivateEngine::loadSubtitles(const Common::Path &path) {
 		return;
 	}
 
-	g_system->showOverlay(false);
-	g_system->clearOverlay();
+	_system->showOverlay(false);
+	_system->clearOverlay();
 	adjustSubtitleSize();
 }
 void PrivateEngine::playVideo(const Common::String &name) {
@@ -2053,7 +2053,7 @@ void PrivateEngine::skipVideo() {
 	if (_subtitles != nullptr) {
 		delete _subtitles;
 		_subtitles = nullptr;
-		g_system->hideOverlay();
+		_system->hideOverlay();
 	}
 	_currentMovie = "";
 }
@@ -2068,7 +2068,7 @@ void PrivateEngine::destroyVideo() {
 	if (_subtitles != nullptr) {
 		delete _subtitles;
 		_subtitles = nullptr;
-		g_system->hideOverlay();
+		_system->hideOverlay();
 	}
 }
 
@@ -2101,7 +2101,7 @@ Graphics::Surface *PrivateEngine::decodeImage(const Common::String &name, byte *
 	if (ncolors < 256 || path.toString('/').hasPrefix("intro")) { // For some reason, requires color remapping
 		currentPalette = (byte *) malloc(3*256);
 		drawScreen();
-		g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
+		_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
 		newImage = oldImage->convertTo(_pixelFormat, currentPalette);
 		remapImage(ncolors, oldImage, oldPalette, newImage, currentPalette);
 		*palette = currentPalette;
@@ -2235,7 +2235,7 @@ void PrivateEngine::fillRect(uint32 color, Common::Rect rect) {
 void PrivateEngine::drawScreenFrame(const byte *newPalette) {
 	debugC(1, kPrivateDebugFunction, "%s(..)", __FUNCTION__);
 	remapImage(256, _frameImage, _framePalette, _mframeImage, newPalette);
-	g_system->copyRectToScreen(_mframeImage->getPixels(), _mframeImage->pitch, 0, 0, _screenW, _screenH);
+	_system->copyRectToScreen(_mframeImage->getPixels(), _mframeImage->pitch, 0, 0, _screenW, _screenH);
 }
 
 void PrivateEngine::loadMaskAndInfo(MaskInfo *m, const Common::String &name, int x, int y, bool drawn) {
@@ -2330,12 +2330,12 @@ void PrivateEngine::drawScreen() {
 
 		if (_needToDrawScreenFrame && _videoDecoder->getCurFrame() >= 0) {
 			const byte *videoPalette = _videoDecoder->getPalette();
-			g_system->getPaletteManager()->setPalette(videoPalette, 0, 256);
+			_system->getPaletteManager()->setPalette(videoPalette, 0, 256);
 			drawScreenFrame(videoPalette);
 			_needToDrawScreenFrame = false;
 		} else if (_videoDecoder->hasDirtyPalette()) {
 			const byte *videoPalette = _videoDecoder->getPalette();
-			g_system->getPaletteManager()->setPalette(videoPalette, 0, 256);
+			_system->getPaletteManager()->setPalette(videoPalette, 0, 256);
 
 			if (_mode == 1) {
 				drawScreenFrame(videoPalette);
@@ -2343,15 +2343,15 @@ void PrivateEngine::drawScreen() {
 		}
 
 		// No use of _compositeSurface, we write the frame directly to the screen in the expected position
-		g_system->copyRectToScreen(frame->getPixels(), frame->pitch, center.x, center.y, frame->w, frame->h);
+		_system->copyRectToScreen(frame->getPixels(), frame->pitch, center.x, center.y, frame->w, frame->h);
 	} else {
 		byte newPalette[256 * 3];
 		_compositeSurface->grabPalette(newPalette, 0, 256);
-		g_system->getPaletteManager()->setPalette(newPalette, 0, 256);
+		_system->getPaletteManager()->setPalette(newPalette, 0, 256);
 
 		if (_mode == 1) {
 			// We can reuse newPalette
-			g_system->getPaletteManager()->grabPalette((byte *) &newPalette, 0, 256);
+			_system->getPaletteManager()->grabPalette((byte *) &newPalette, 0, 256);
 			drawScreenFrame((byte *) &newPalette);
 		}
 
@@ -2398,12 +2398,12 @@ void PrivateEngine::drawScreen() {
 
 		Common::Rect w(_origin.x, _origin.y, _screenW - _origin.x, _screenH - _origin.y);
 		Graphics::Surface sa = _compositeSurface->getSubArea(w);
-		g_system->copyRectToScreen(sa.getPixels(), sa.pitch, _origin.x, _origin.y, sa.w, sa.h);
+		_system->copyRectToScreen(sa.getPixels(), sa.pitch, _origin.x, _origin.y, sa.w, sa.h);
 	}
 
 	if (_subtitles && _videoDecoder && !_videoDecoder->isPaused())
 		_subtitles->drawSubtitle(_videoDecoder->getTime(), false, _sfxSubtitles);
-	g_system->updateScreen();
+	_system->updateScreen();
 }
 
 bool PrivateEngine::getRandomBool(uint p) {
@@ -2474,11 +2474,11 @@ static void timerCallback(void *refCon) {
 }
 
 bool PrivateEngine::installTimer(uint32 delay, Common::String *ns) {
-	return g_system->getTimerManager()->installTimerProc(&timerCallback, delay, ns, "timerCallback");
+	return _system->getTimerManager()->installTimerProc(&timerCallback, delay, ns, "timerCallback");
 }
 
 void PrivateEngine::removeTimer() {
-	g_system->getTimerManager()->removeTimerProc(&timerCallback);
+	_system->getTimerManager()->removeTimerProc(&timerCallback);
 }
 
 // Diary


Commit: 2bc56a6f62beb8c18b2587c10f247cca1c25aa82
    https://github.com/scummvm/scummvm/commit/2bc56a6f62beb8c18b2587c10f247cca1c25aa82
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:30-08:00

Commit Message:
PRIVATE: Remove `inBox` method

Changed paths:
    engines/private/private.cpp
    engines/private/private.h


diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 7980c67cefc..8d68c0a47da 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -752,10 +752,6 @@ bool PrivateEngine::inMask(Graphics::Surface *surf, Common::Point mousePos) {
 	return (surf->getPixel(mousePos.x, mousePos.y) != _transparentColor);
 }
 
-bool PrivateEngine::inBox(const Common::Rect &box, Common::Point mousePos) {
-	return box.contains(mousePos);
-}
-
 bool PrivateEngine::cursorMask(Common::Point mousePos) {
 	bool inside = false;
 	for (MaskList::const_iterator it = _masks.begin(); it != _masks.end(); ++it) {
@@ -994,7 +990,7 @@ bool PrivateEngine::selectLocation(const Common::Point &mousePos) {
 	for (auto &it : maps.locationList) {
 		const Private::Symbol *sym = maps.locations.getVal(it);
 		if (sym->u.val) {
-			if (inBox(_locationMasks[i].box, mousePos)) {
+			if (_locationMasks[i].box.contains(mousePos)) {
 				bool diaryPageSet = false;
 				for (uint j = 0; j < _diaryPages.size(); j++) {
 					if (_diaryPages[j].locationID == totalLocations + 1) {
diff --git a/engines/private/private.h b/engines/private/private.h
index deb6908d080..97105a0ba87 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -284,7 +284,6 @@ public:
 	void drawMask(Graphics::Surface *);
 	void fillRect(uint32, Common::Rect);
 	bool inMask(Graphics::Surface *, Common::Point);
-	bool inBox(const Common::Rect &box, Common::Point mousePos);
 	uint32 _transparentColor;
 	Common::Rect _screenRect;
 	Common::String _framePath;


Commit: dc48c48147d4c4a10284b53e9d560307d75e310e
    https://github.com/scummvm/scummvm/commit/dc48c48147d4c4a10284b53e9d560307d75e310e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:31-08:00

Commit Message:
PRIVATE: Rename debug channel to match enum

Changed paths:
    engines/private/detection.cpp


diff --git a/engines/private/detection.cpp b/engines/private/detection.cpp
index ef504bacc9e..29334ed0885 100644
--- a/engines/private/detection.cpp
+++ b/engines/private/detection.cpp
@@ -27,7 +27,7 @@
 #include "private/detection.h"
 
 static const DebugChannelDef debugFlagList[] = {
-	{Private::kPrivateDebugFunction, "functions", "Function execution debug channel"},
+	{Private::kPrivateDebugFunction, "function", "Function execution debug channel"},
 	{Private::kPrivateDebugCode, "code", "Code execution debug channel"},
 	{Private::kPrivateDebugScript, "script", "Script execution debug channel"},
 	DEBUG_CHANNEL_END


Commit: acf3859a87b04f456526c420a6819b730cb3ec7d
    https://github.com/scummvm/scummvm/commit/acf3859a87b04f456526c420a6819b730cb3ec7d
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:31-08:00

Commit Message:
PRIVATE: Reduce string creation in `restartGame()`

Changed paths:
    engines/private/private.cpp


diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 8d68c0a47da..41812b1ceb7 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -1508,9 +1508,10 @@ bool PrivateEngine::hasFeature(EngineFeature f) const {
 void PrivateEngine::restartGame() {
 	debugC(1, kPrivateDebugFunction, "restartGame");
 
+	Common::String alternateGameVariableName = getAlternateGameVariable();
 	for (NameList::iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) {
 		Private::Symbol *sym = maps.variables.getVal(*it);
-		if (*(sym->name) != getAlternateGameVariable())
+		if (*(sym->name) != alternateGameVariableName)
 			sym->u.val = 0;
 	}
 


Commit: 5b5013c19323c47133027ae06314c0c18e68eec3
    https://github.com/scummvm/scummvm/commit/5b5013c19323c47133027ae06314c0c18e68eec3
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:31-08:00

Commit Message:
PRIVATE: Implement timer in main thread

Fixes unsychronized access to engine data

Changed paths:
    engines/private/funcs.cpp
    engines/private/private.cpp
    engines/private/private.h


diff --git a/engines/private/funcs.cpp b/engines/private/funcs.cpp
index 20078fa1031..10e456527ad 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -786,10 +786,9 @@ static void fTimer(ArgArray args) {
 	else
 		debugC(1, kPrivateDebugScript, "Timer(%d, %s)", args[0].u.val, args[1].u.str);
 
-	int32 delay = 1000000 * args[0].u.val;
+	int32 delay = args[0].u.val * 1000; // seconds => milliseconds
 	if (delay > 0) {
-		if (!g_private->installTimer(delay, args[1].u.sym->name))
-			error("Timer installation failed!");
+		g_private->setTimer(delay, *(args[1].u.sym->name));
 	} else if (delay == 0) {
 		g_private->_nextSetting = *(args[1].u.sym->name);
 	} else {
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 41812b1ceb7..5d8f5f71aac 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -122,6 +122,9 @@ PrivateEngine::PrivateEngine(OSystem *syst, const ADGameDescription *gd)
 		_safeDigitArea[d].clear();
 		_safeDigitRect[d] = Common::Rect(0, 0);
 	}
+
+	// Timer
+	clearTimer();
 }
 
 PrivateEngine::~PrivateEngine() {
@@ -354,6 +357,7 @@ Common::Error PrivateEngine::run() {
 
 	while (!shouldQuit()) {
 		bool mouseMoved = false;
+		checkTimer();
 		checkPhoneCall();
 
 		while (_system->getEventManager()->pollEvent(event)) {
@@ -423,7 +427,7 @@ Common::Error PrivateEngine::run() {
 
 		// Movies
 		if (!_nextMovie.empty()) {
-			removeTimer();
+			clearTimer();
 			_videoDecoder = new Video::SmackerDecoder();
 			playVideo(_nextMovie);
 			_currentMovie = _nextMovie;
@@ -455,7 +459,7 @@ Common::Error PrivateEngine::run() {
 		}
 
 		if (!_nextSetting.empty()) {
-			removeTimer();
+			clearTimer();
 			debugC(1, kPrivateDebugFunction, "Executing %s", _nextSetting.c_str());
 			clearAreas();
 			_currentSetting = _nextSetting;
@@ -491,7 +495,6 @@ Common::Error PrivateEngine::run() {
 			}
 		}
 	}
-	removeTimer();
 	return Common::kNoError;
 }
 
@@ -1548,6 +1551,9 @@ void PrivateEngine::restartGame() {
 
 	// Wall Safe
 	initializeWallSafeValue();
+
+	// Timer
+	clearTimer();
 }
 
 Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream) {
@@ -2464,18 +2470,30 @@ Common::String PrivateEngine::getRandomPhoneClip(const char *clip, int i, int j)
 	return Common::String::format("%s%02d", clip, r);
 }
 
-// Timers
-static void timerCallback(void *refCon) {
-	g_private->removeTimer();
-	g_private->_nextSetting = *(Common::String *)refCon;
+// Timer
+
+void PrivateEngine::setTimer(uint32 delay, const Common::String &setting) {
+	_timerSetting = setting;
+	_timerStartTime = _system->getMillis();
+	_timerDelay = delay;
 }
 
-bool PrivateEngine::installTimer(uint32 delay, Common::String *ns) {
-	return _system->getTimerManager()->installTimerProc(&timerCallback, delay, ns, "timerCallback");
+void PrivateEngine::clearTimer() {
+	_timerSetting.clear();
+	_timerStartTime = 0;
+	_timerDelay = 0;
 }
 
-void PrivateEngine::removeTimer() {
-	_system->getTimerManager()->removeTimerProc(&timerCallback);
+void PrivateEngine::checkTimer() {
+	if (_timerSetting.empty()) {
+		return;
+	}
+	
+	uint32 now = _system->getMillis();
+	if (now - _timerStartTime >= _timerDelay) {
+		_nextSetting = _timerSetting;
+		clearTimer();
+	}
 }
 
 // Diary
diff --git a/engines/private/private.h b/engines/private/private.h
index 97105a0ba87..4b6a01695a4 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -440,9 +440,13 @@ public:
 	// Random values
 	bool getRandomBool(uint);
 
-	// Timers
-	bool installTimer(uint32, Common::String *);
-	void removeTimer();
+	// Timer
+	Common::String _timerSetting;
+	uint32 _timerStartTime;
+	uint32 _timerDelay;
+	void setTimer(uint32 duration, const Common::String &setting);
+	void clearTimer();
+	void checkTimer();
 
 	// VM objects
 	RectList _rects; // created by fCRect


Commit: 8ff93cc3628e26f278c54782fa7cba26f43fbcea
    https://github.com/scummvm/scummvm/commit/8ff93cc3628e26f278c54782fa7cba26f43fbcea
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:31-08:00

Commit Message:
PRIVATE: Implement skipping timer

Escape can now skip delays during introduction, as in the original

Changed paths:
    engines/private/funcs.cpp
    engines/private/private.cpp
    engines/private/private.h


diff --git a/engines/private/funcs.cpp b/engines/private/funcs.cpp
index 10e456527ad..20d643a0cd9 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -788,7 +788,11 @@ static void fTimer(ArgArray args) {
 
 	int32 delay = args[0].u.val * 1000; // seconds => milliseconds
 	if (delay > 0) {
-		g_private->setTimer(delay, *(args[1].u.sym->name));
+		Common::String skipSetting;
+		if (args.size() == 3) {
+			skipSetting = *(args[2].u.sym->name);
+		}
+		g_private->setTimer(delay, *(args[1].u.sym->name), skipSetting);
 	} else if (delay == 0) {
 		g_private->_nextSetting = *(args[1].u.sym->name);
 	} else {
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 5d8f5f71aac..8534971b396 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -366,7 +366,11 @@ Common::Error PrivateEngine::run() {
 			switch (event.type) {
 			case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
 				if (event.customType == kActionSkip) {
-					skipVideo();
+					if (!_timerSkipSetting.empty()) {
+						skipTimer();
+					} else {
+						skipVideo();
+					}
 				}
 				break;
 
@@ -2472,18 +2476,25 @@ Common::String PrivateEngine::getRandomPhoneClip(const char *clip, int i, int j)
 
 // Timer
 
-void PrivateEngine::setTimer(uint32 delay, const Common::String &setting) {
+void PrivateEngine::setTimer(uint32 delay, const Common::String &setting, const Common::String &skipSetting) {
 	_timerSetting = setting;
+	_timerSkipSetting = skipSetting;
 	_timerStartTime = _system->getMillis();
 	_timerDelay = delay;
 }
 
 void PrivateEngine::clearTimer() {
 	_timerSetting.clear();
+	_timerSkipSetting.clear();
 	_timerStartTime = 0;
 	_timerDelay = 0;
 }
 
+void PrivateEngine::skipTimer() {
+	_nextSetting = _timerSkipSetting;
+	clearTimer();
+}
+
 void PrivateEngine::checkTimer() {
 	if (_timerSetting.empty()) {
 		return;
diff --git a/engines/private/private.h b/engines/private/private.h
index 4b6a01695a4..f0b00a9e7fc 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -442,10 +442,12 @@ public:
 
 	// Timer
 	Common::String _timerSetting;
+	Common::String _timerSkipSetting;
 	uint32 _timerStartTime;
 	uint32 _timerDelay;
-	void setTimer(uint32 duration, const Common::String &setting);
+	void setTimer(uint32 duration, const Common::String &setting, const Common::String &skipSetting);
 	void clearTimer();
+	void skipTimer();
 	void checkTimer();
 
 	// VM objects


Commit: db5d1f7bbf761e66ea14a321765c16d2b8767430
    https://github.com/scummvm/scummvm/commit/db5d1f7bbf761e66ea14a321765c16d2b8767430
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:32-08:00

Commit Message:
PRIVATE: Update PhoneClip implementation

Changed paths:
    engines/private/funcs.cpp
    engines/private/private.cpp
    engines/private/private.h
    engines/private/savegame.h


diff --git a/engines/private/funcs.cpp b/engines/private/funcs.cpp
index 20d643a0cd9..6ed02a06642 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -668,20 +668,7 @@ static void fAddSound(Common::String sound, const char *t, Symbol *flag = nullpt
 		g_private->_AMRadio.push_back(sound);
 	else if (strcmp(t, "PoliceClip") == 0)
 		g_private->_policeRadio.push_back(sound);
-	else if (strcmp(t, "PhoneClip") == 0) {
-		// This condition will avoid adding the same phone call twice,
-		// it is unclear why this could be useful, but it looks like a bug
-		// in the original scripts
-		if (g_private->_playedPhoneClips.contains(sound))
-			return;
-
-		g_private->_playedPhoneClips.setVal(sound, true);
-		PhoneInfo p;
-		p.sound = sound;
-		p.flag = flag;
-		p.val = val;
-		g_private->_phone.push_back(p);
-	} else
+	else
 		error("error: invalid sound type %s", t);
 }
 
@@ -708,17 +695,19 @@ static void fPhoneClip(ArgArray args) {
 		debugC(1, kPrivateDebugScript, "Unimplemented PhoneClip special case");
 		return;
 	}
-	int i = args[2].u.val;
-	int j = args[3].u.val;
-	Symbol *flag = g_private->maps.lookupVariable(args[4].u.sym->name);
+	assert(args.size() == 6);
+	debugC(1, kPrivateDebugScript, "PhoneClip(%s,%d,%d,%d,%s,%d)",
+		args[0].u.str, args[1].u.val, args[2].u.val, args[3].u.val, args[4].u.sym->name->c_str(), args[5].u.val);
 
-	if (i == j)
-		fAddSound(args[0].u.str, "PhoneClip", flag, args[5].u.val);
-	else {
-		assert(i < j);
-		Common::String sound = g_private->getRandomPhoneClip(args[0].u.str, i, j);
-		fAddSound(sound, "PhoneClip", flag, args[5].u.val);
-	}
+	const char *name = args[0].u.str;
+	bool once = (args[1].u.val != 0);
+	int startIndex = args[2].u.val;
+	int endIndex = args[3].u.val;
+	Common::String *flagName = args[4].u.sym->name;
+	int flagValue = args[5].u.val;
+	assert(startIndex <= endIndex);
+
+	g_private->addPhone(name, once, startIndex, endIndex, *flagName, flagValue);
 }
 
 static void fSoundArea(ArgArray args) {
@@ -760,7 +749,7 @@ static void fSoundArea(ArgArray args) {
 		m.flag1 = nullptr;
 		m.flag2 = nullptr;
 		g_private->_phoneArea = m;
-		g_private->_masks.push_front(m);
+		g_private->initializePhoneOnDesktop();
 	} else
 		error("Invalid type for SoundArea");
 }
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 8534971b396..93e1277b58e 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -163,6 +163,11 @@ PrivateEngine::~PrivateEngine() {
 		}
 	}
 
+	if (_phoneArea.surf != nullptr) {
+		_phoneArea.surf->free();
+		delete _phoneArea.surf;
+	}
+
 	for (uint i = 0; i < ARRAYSIZE(_safeDigitArea); i++) {
 		if (_safeDigitArea[i].surf != nullptr) {
 			_safeDigitArea[i].surf->free();
@@ -535,6 +540,10 @@ void PrivateEngine::clearAreas() {
 	_saveGameMask.clear();
 	_policeRadioArea.clear();
 	_AMRadioArea.clear();
+	if (_phoneArea.surf != nullptr) {
+		_phoneArea.surf->free();
+		delete _phoneArea.surf;
+	}
 	_phoneArea.clear();
 	_dossierPageMask.clear();
 	_dossierNextSuspectMask.clear();
@@ -681,6 +690,9 @@ void PrivateEngine::updateCursor(Common::Point mousePos) {
 	if (cursorPauseMovie(mousePos)) {
 		return;
 	}
+	if (cursorPhoneArea(mousePos)) {
+		return;
+	}
 	if (cursorSafeDigit(mousePos)) {
 		return;
 	}
@@ -1265,39 +1277,6 @@ void PrivateEngine::selectPoliceRadioArea(Common::Point mousePos) {
 	}
 }
 
-void PrivateEngine::checkPhoneCall() {
-	if (_phoneArea.surf == nullptr)
-		return;
-
-	if (_phone.empty())
-		return;
-
-	if (!_mixer->isSoundHandleActive(_fgSoundHandle))
-		playSound(_phonePrefix + _phoneCallSound, 1, false, false);
-}
-
-void PrivateEngine::selectPhoneArea(Common::Point mousePos) {
-	if (_phoneArea.surf == nullptr)
-		return;
-
-	if (_phone.empty())
-		return;
-
-	if (inMask(_phoneArea.surf, mousePos)) {
-		const PhoneInfo &i = _phone.front();
-		// -100 indicates that the variable should be decremented
-		if (i.val == -100) {
-			setSymbol(i.flag, i.flag->u.val - 1);
-		} else {
-			setSymbol(i.flag, i.val);
-		}
-		Common::String sound = _phonePrefix + i.sound + ".wav";
-		playSound(sound, 1, true, false);
-		_phone.pop_front();
-		_nextSetting = getListenToPhoneSetting();
-	}
-}
-
 void PrivateEngine::addDossier(Common::String &page1, Common::String &page2) {
 	// Each dossier page can only be added once.
 	// Do this even when loading games to fix saves with duplicates.
@@ -1415,6 +1394,166 @@ bool PrivateEngine::selectDossierPrevSuspect(Common::Point mousePos) {
 	return false;
 }
 
+void PrivateEngine::addPhone(const Common::String &name, bool once, int startIndex, int endIndex, const Common::String &flagName, int flagValue) {
+	// lookup phone clip by name and index range
+	PhoneInfo *phone = nullptr;
+	for (PhoneList::iterator it = _phones.begin(); it != _phones.end(); ++it) {
+		if (it->name == name && it->startIndex == startIndex && it->endIndex == endIndex) {
+			phone = &(*it);
+			break;
+		}
+	}
+
+	// add or update phone clip
+	if (phone == nullptr) {
+		PhoneInfo newPhone;
+		newPhone.name = name;
+		newPhone.once = once;
+		newPhone.startIndex = startIndex;
+		newPhone.endIndex = endIndex;
+		newPhone.flagName = flagName;
+		newPhone.flagValue = flagValue;
+		newPhone.status = kPhoneStatusWaiting;
+		newPhone.callCount = 0;
+		newPhone.soundIndex = 0;
+		// add single clip or a range of clips that occur in a random order
+		if (startIndex == endIndex) {
+			Common::String sound = name + ".wav";
+			newPhone.sounds.push_back(sound);
+		} else {
+			for (int i = startIndex; i <= endIndex; i++) {
+				Common::String sound = Common::String::format("%s%02d.wav", name.c_str(), i);
+				newPhone.sounds.push_back(sound);
+			}
+			// shuffle
+			for (uint i = newPhone.sounds.size() - 1; i > 0; i--) {
+				uint n = _rnd->getRandomNumber(i);
+				SWAP<Common::String>(newPhone.sounds[i], newPhone.sounds[n]);
+			}
+		}
+		// add to front of list; calls occur in reverse order
+		_phones.push_front(newPhone);
+	} else {
+		// update an available phone clip's state if its sounds haven't been played yet
+		if (phone->soundIndex < phone->sounds.size()) {
+			// reset the call count
+			phone->callCount = 0;
+
+			// the first PhoneClip() call does not cause the phone clip to ring,
+			// but the second call does. if a phone clip has multiple sounds and
+			// one has been answered then its status changes to waiting so that
+			// the next PhoneClip() call will make the next sound available.
+			if (phone->status == kPhoneStatusWaiting) {
+				phone->status = kPhoneStatusAvailable;
+			} else if (phone->status == kPhoneStatusAnswered) {
+				phone->status = kPhoneStatusWaiting;
+			}
+		}
+	}
+}
+
+void PrivateEngine::initializePhoneOnDesktop() {
+	// any phone clips that were missed, or left ringing, are available
+	// unless they are phone clips that only occur once.
+	for (PhoneList::iterator it = _phones.begin(); it != _phones.end(); ++it) {
+		if (!it->once && (it->status == kPhoneStatusCalling || it->status == kPhoneStatusMissed)) {
+			it->status = kPhoneStatusAvailable;
+		}
+	}
+}
+
+void PrivateEngine::checkPhoneCall() {
+	if (_phoneArea.surf == nullptr) {
+		return;
+	}
+
+	if (isSoundActive()) {
+		return;
+	}
+
+	// any phone clips that were calling have been missed
+	for (PhoneList::iterator it = _phones.begin(); it != _phones.end(); ++it) {
+		if (it->status == kPhoneStatusCalling) {
+			it->status = kPhoneStatusMissed;
+		}
+	}
+
+	// get the next available phone clip
+	PhoneInfo *phone = nullptr;
+	for (PhoneList::iterator it = _phones.begin(); it != _phones.end(); ++it) {
+		if (it->status == kPhoneStatusAvailable &&
+			it->soundIndex < it->sounds.size() &&
+			it->callCount < (it->once ? 1 : 3)) {
+			phone = &(*it);
+			break;
+		}
+	}
+	if (phone == nullptr) {
+		return;
+	}
+
+	phone->status = kPhoneStatusCalling;
+	phone->callCount++;
+	playPhoneCallSound();
+}
+
+bool PrivateEngine::cursorPhoneArea(Common::Point mousePos) {
+	if (_phoneArea.surf == nullptr) {
+		return false;
+	}
+
+	if (!_mixer->isSoundHandleActive(_phoneCallSoundHandle)) {
+		return false;
+	}
+
+	if (inMask(_phoneArea.surf, mousePos)) {
+		changeCursor(_phoneArea.cursor);
+		return true;
+	}
+
+	return false;
+}
+
+void PrivateEngine::selectPhoneArea(Common::Point mousePos) {
+	if (_phoneArea.surf == nullptr) {
+		return;
+	}
+
+	if (!_mixer->isSoundHandleActive(_phoneCallSoundHandle)) {
+		return;
+	}
+
+	if (inMask(_phoneArea.surf, mousePos)) {
+		// get phone clip to answer
+		PhoneInfo *phone = nullptr;
+		for (PhoneList::iterator it = _phones.begin(); it != _phones.end(); ++it) {
+			if (it->status == kPhoneStatusCalling) {
+				phone = &(*it);
+				break;
+			}
+		}
+		if (phone == nullptr) {
+			return;
+		}
+
+		// phone clip has been answered, select sound
+		phone->status = kPhoneStatusAnswered;
+		Common::String sound = _phonePrefix + phone->sounds[phone->soundIndex];
+		phone->soundIndex++;
+
+		// -100 indicates that the variable should be decremented
+		Symbol *flag = maps.lookupVariable(&(phone->flagName));
+		if (phone->flagValue == -100) {
+			setSymbol(flag, flag->u.val - 1);
+		} else {
+			setSymbol(flag, phone->flagValue);
+		}
+
+		playSound(sound, 1, true, false);
+		_nextSetting = getListenToPhoneSetting();
+	}
+}
+
 void PrivateEngine::initializeWallSafeValue() {
 	if (isDemo()) {
 		return;
@@ -1539,8 +1678,7 @@ void PrivateEngine::restartGame() {
 	// Sounds
 	_AMRadio.clear();
 	_policeRadio.clear();
-	_phone.clear();
-	_playedPhoneClips.clear();
+	_phones.clear();
 
 	// Movies
 	_repeatedMovieExit = "";
@@ -1662,15 +1800,24 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
 	}
 
 	size = stream->readUint32LE();
-	_phone.clear();
+	_phones.clear();
 	PhoneInfo p;
 	Common::String name;
 	for (uint32 j = 0; j < size; ++j) {
-		p.sound = stream->readString();
-		name = stream->readString();
-		p.flag = maps.lookupVariable(&name);
-		p.val = stream->readUint32LE();
-		_phone.push_back(p);
+		p.name = stream->readString();
+		p.once = (stream->readByte() == 1);
+		p.startIndex = stream->readSint32LE();
+		p.endIndex = stream->readSint32LE();
+		p.flagName = stream->readString();
+		p.flagValue = stream->readSint32LE();
+		p.status = (PhoneStatus)stream->readByte();
+		p.callCount = stream->readSint32LE();
+		p.soundIndex = stream->readUint32LE();
+		uint32 phoneSoundsSize = stream->readUint32LE();
+		for (uint32 i = 0; i < phoneSoundsSize; i++) {
+			p.sounds.push_back(stream->readString());
+		}
+		_phones.push_back(p);
 	}
 
 	// Played media
@@ -1681,12 +1828,6 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
 		_playedMovies.setVal(stream->readString(), true);
 	}
 
-	_playedPhoneClips.clear();
-	size = stream->readUint32LE();
-	for (uint32 i = 0; i < size; ++i) {
-		_playedPhoneClips.setVal(stream->readString(), true);
-	}
-
 	// VSPicture
 	_nextVS = stream->readString();
 
@@ -1796,13 +1937,25 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
 		stream->writeByte(0);
 	}
 
-	stream->writeUint32LE(_phone.size());
-	for (PhoneList::const_iterator it = _phone.begin(); it != _phone.end(); ++it) {
-		stream->writeString(it->sound);
+	// Phone
+	stream->writeUint32LE(_phones.size());
+	for (PhoneList::const_iterator it = _phones.begin(); it != _phones.end(); ++it) {
+		stream->writeString(it->name);
 		stream->writeByte(0);
-		stream->writeString(*it->flag->name);
+		stream->writeByte(it->once ? 1 : 0);
+		stream->writeSint32LE(it->startIndex);
+		stream->writeSint32LE(it->endIndex);
+		stream->writeString(it->flagName);
 		stream->writeByte(0);
-		stream->writeUint32LE(it->val);
+		stream->writeSint32LE(it->flagValue);
+		stream->writeByte(it->status);
+		stream->writeSint32LE(it->callCount);
+		stream->writeUint32LE(it->soundIndex);
+		stream->writeUint32LE(it->sounds.size());
+		for (uint i = 0; i < it->sounds.size(); i++) {
+			stream->writeString(it->sounds[i]);
+			stream->writeByte(0);
+		}
 	}
 
 	// Played media
@@ -1815,12 +1968,6 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
 		stream->writeByte(0);
 	}
 
-	stream->writeUint32LE(_playedPhoneClips.size());
-	for (PlayedMediaTable::const_iterator it = _playedPhoneClips.begin(); it != _playedPhoneClips.end(); ++it) {
-		stream->writeString(it->_key);
-		stream->writeByte(0);
-	}
-
 	// VSPicture
 	stream->writeString(_nextVS);
 	stream->writeByte(0);
@@ -1879,6 +2026,7 @@ void PrivateEngine::playSound(const Common::String &name, uint loops, bool stopO
 		sh = &_bgSoundHandle;
 	} else {
 		_mixer->stopHandle(_fgSoundHandle);
+		_mixer->stopHandle(_phoneCallSoundHandle);
 		sh = &_fgSoundHandle;
 	}
 
@@ -1886,6 +2034,18 @@ void PrivateEngine::playSound(const Common::String &name, uint loops, bool stopO
 	loadSubtitles(path);
 }
 
+void PrivateEngine::playPhoneCallSound() {
+	debugC(1, kPrivateDebugFunction, "%s()", __FUNCTION__);
+
+	Common::Path path = convertPath(_phonePrefix + _phoneCallSound);
+	Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(path);
+	if (!file) {
+		error("unable to find sound file %s", path.toString().c_str());
+	}
+	Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
+	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_phoneCallSoundHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume);
+}
+
 bool PrivateEngine::isSoundActive() {
 	return _mixer->isSoundIDActive(-1);
 }
@@ -2082,11 +2242,11 @@ void PrivateEngine::destroyVideo() {
 void PrivateEngine::stopSound(bool all) {
 	debugC(1, kPrivateDebugFunction, "%s(%d)", __FUNCTION__, all);
 
+	_mixer->stopHandle(_fgSoundHandle);
+	_mixer->stopHandle(_phoneCallSoundHandle);
+
 	if (all) {
-		_mixer->stopHandle(_fgSoundHandle);
 		_mixer->stopHandle(_bgSoundHandle);
-	} else {
-		_mixer->stopHandle(_fgSoundHandle);
 	}
 }
 
@@ -2469,11 +2629,6 @@ Common::String PrivateEngine::getLeaveSound() {
 	return _globalAudioPath + sounds[r];
 }
 
-Common::String PrivateEngine::getRandomPhoneClip(const char *clip, int i, int j) {
-	uint r = i + _rnd->getRandomNumber(j - i);
-	return Common::String::format("%s%02d", clip, r);
-}
-
 // Timer
 
 void PrivateEngine::setTimer(uint32 delay, const Common::String &setting, const Common::String &skipSetting) {
diff --git a/engines/private/private.h b/engines/private/private.h
index f0b00a9e7fc..b1261a2878b 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -118,10 +118,25 @@ typedef struct MaskInfo {
 	}
 } MaskInfo;
 
+enum PhoneStatus : byte {
+	kPhoneStatusWaiting,
+	kPhoneStatusAvailable,
+	kPhoneStatusCalling,
+	kPhoneStatusMissed,
+	kPhoneStatusAnswered
+};
+
 typedef struct PhoneInfo {
-	Common::String sound;
-	Symbol *flag;
-	int val;
+	Common::String name;
+	bool once;
+	int startIndex;
+	int endIndex;
+	Common::String flagName;
+	int flagValue;
+	PhoneStatus status;
+	int callCount;
+	uint32 soundIndex;
+	Common::Array<Common::String> sounds;
 } PhoneInfo;
 
 typedef struct DossierInfo {
@@ -203,6 +218,7 @@ public:
 
 	Audio::SoundHandle _fgSoundHandle;
 	Audio::SoundHandle _bgSoundHandle;
+	Audio::SoundHandle _phoneCallSoundHandle;
 	Video::SmackerDecoder *_videoDecoder;
 	Video::SmackerDecoder *_pausedVideo;
 
@@ -387,7 +403,6 @@ public:
 	bool _modified;
 
 	PlayedMediaTable _playedMovies;
-	PlayedMediaTable _playedPhoneClips;
 	Common::String _repeatedMovieExit;
 
 	// Masks/Exits
@@ -396,6 +411,7 @@ public:
 
 	// Sounds
 	void playSound(const Common::String &, uint, bool, bool);
+	void playPhoneCallSound();
 	void stopSound(bool);
 	bool isSoundActive();
 	void waitForSoundToStop();
@@ -413,18 +429,21 @@ public:
 	Common::String _infaceRadioPath;
 	MaskInfo _AMRadioArea;
 	MaskInfo _policeRadioArea;
-	MaskInfo _phoneArea;
-	Common::String _phonePrefix;
-	Common::String _phoneCallSound;
 	SoundList _AMRadio;
 	SoundList _policeRadio;
-	PhoneList _phone;
-
-	Common::String getRandomPhoneClip(const char *, int, int);
 	void selectAMRadioArea(Common::Point);
 	void selectPoliceRadioArea(Common::Point);
-	void selectPhoneArea(Common::Point);
+
+	// Phone
+	MaskInfo _phoneArea;
+	Common::String _phonePrefix;
+	Common::String _phoneCallSound;
+	PhoneList _phones;
+	void addPhone(const Common::String &name, bool once, int startIndex, int endIndex, const Common::String &flagName, int flagValue);
+	void initializePhoneOnDesktop();
 	void checkPhoneCall();
+	bool cursorPhoneArea(Common::Point mousePos);
+	void selectPhoneArea(Common::Point mousePos);
 
 	// Safe
 	Common::String _safeNumberPath;
diff --git a/engines/private/savegame.h b/engines/private/savegame.h
index 588bd103f48..ecf2f291873 100644
--- a/engines/private/savegame.h
+++ b/engines/private/savegame.h
@@ -36,12 +36,13 @@ namespace Private {
 //
 // Version - new/changed feature
 // =============================
+//       2 - Phone clip detailed state (December 2025)
 //       1 - Metadata header and more game state (November 2025)
 //
 // Earlier versions did not have a header and not supported.
 
-const uint16 kCurrentSavegameVersion = 1;
-const uint16 kMinimumSavegameVersion = 1;
+const uint16 kCurrentSavegameVersion = 2;
+const uint16 kMinimumSavegameVersion = 2;
 
 struct SavegameMetadata {
 	uint16 version;


Commit: 0df1ed7e55a384fd4e789f4b7bfa9aebbeff88c5
    https://github.com/scummvm/scummvm/commit/0df1ed7e55a384fd4e789f4b7bfa9aebbeff88c5
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:23:32-08:00

Commit Message:
PRIVATE: Reduce global usage

Changed paths:
    engines/private/private.cpp


diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 93e1277b58e..8b2bb0db3f4 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -632,14 +632,14 @@ void PrivateEngine::completePoliceBust() {
 	// Select the movie for BustMovie() to play
 	_policeBustMovie =
 		Common::String::format("po/animatio/spoc%02dxs.smk",
-			kPoliceBustVideos[g_private->_policeBustMovieIndex]);
+			kPoliceBustVideos[_policeBustMovieIndex]);
 
 	// Play audio on the second bust movie
 	if (kPoliceBustVideos[_policeBustMovieIndex] == 2) {
 		Common::String s("global/transiti/audio/spoc02VO.wav");
-		g_private->playSound(s, 1, true, false);
-		g_private->changeCursor("default");
-		g_private->waitForSoundToStop();
+		playSound(s, 1, true, false);
+		changeCursor("default");
+		waitForSoundToStop();
 	}
 
 	// Cycle to the next movie and wrap around
@@ -1592,7 +1592,7 @@ void PrivateEngine::addSafeDigit(uint32 d, Common::Rect *rect) {
 	_safeDigitRect[d] = *rect;
 	int digitValue = getSafeDigit(d);
 	m.surf = loadMask(Common::String::format(_safeNumberPath.c_str(), digitValue), _safeDigitRect[d].left, _safeDigitRect[d].top, true);
-	m.cursor = g_private->getExitCursor();
+	m.cursor = getExitCursor();
 	m.nextSetting = _currentSetting;
 	m.flag1 = nullptr;
 	m.flag2 = nullptr;
@@ -2051,12 +2051,12 @@ bool PrivateEngine::isSoundActive() {
 }
 
 void PrivateEngine::waitForSoundToStop() {
-	while (g_private->isSoundActive())
-		g_private->ignoreEvents();
+	while (isSoundActive())
+		ignoreEvents();
 
 	uint32 i = 100;
 	while (i--) // one second extra
-		g_private->ignoreEvents();
+		ignoreEvents();
 }
 
 void PrivateEngine::adjustSubtitleSize() {
@@ -2695,7 +2695,7 @@ void PrivateEngine::loadLocations(const Common::Rect &rect) {
 
 		MaskInfo m;
 		loadMaskAndInfo(&m, s, rect.left + 90, rect.top + offset, true);
-		m.cursor = g_private->getExitCursor();
+		m.cursor = getExitCursor();
 		m.nextSetting = getDiaryMiddleSetting();
 		m.flag1 = nullptr;
 		m.flag2 = nullptr;
@@ -2730,7 +2730,7 @@ void PrivateEngine::loadMemories(const Common::Rect &rect, uint rightPageOffset,
 	for (uint i = 0; i < _diaryPages[_currentDiaryPage].memories.size(); i++) {
 		MaskInfo m;
 		m.surf = loadMask(_diaryPages[_currentDiaryPage].memories[i].image, rect.left + horizontalOffset, rect.top + currentVerticalOffset, true);
-		m.cursor = g_private->getExitCursor();
+		m.cursor = getExitCursor();
 		m.nextSetting = getDiaryMiddleSetting();
 		m.flag1 = nullptr;
 		m.flag2 = nullptr;


Commit: 14f7b8320e02fa1c8ed6d1f9a55e01c7d78be02f
    https://github.com/scummvm/scummvm/commit/14f7b8320e02fa1c8ed6d1f9a55e01c7d78be02f
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-08T11:24:13-08:00

Commit Message:
PRIVATE: Fix phone bug when loading game

Changed paths:
    engines/private/private.cpp


diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index 8b2bb0db3f4..b83cffb76c0 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -1801,9 +1801,8 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
 
 	size = stream->readUint32LE();
 	_phones.clear();
-	PhoneInfo p;
-	Common::String name;
 	for (uint32 j = 0; j < size; ++j) {
+		PhoneInfo p;
 		p.name = stream->readString();
 		p.once = (stream->readByte() == 1);
 		p.startIndex = stream->readSint32LE();




More information about the Scummvm-git-logs mailing list