[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