[Scummvm-git-logs] scummvm master -> 2a75bf3c8d747c9b11ad1e8b3cb7e4ae8a66df1e

neuromancer noreply at scummvm.org
Fri Dec 5 07:25:25 UTC 2025


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

Summary:
2a75bf3c8d PRIVATE: Update PhoneClip implementation


Commit: 2a75bf3c8d747c9b11ad1e8b3cb7e4ae8a66df1e
    https://github.com/scummvm/scummvm/commit/2a75bf3c8d747c9b11ad1e8b3cb7e4ae8a66df1e
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-05T08:25:21+01: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;




More information about the Scummvm-git-logs mailing list