[Scummvm-git-logs] scummvm master -> 319ebaf47c48a73297f84f8d31413d375d968ffa
neuromancer
noreply at scummvm.org
Tue Dec 9 09:59:57 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:
319ebaf47c PRIVATE: Fully implement AMRadioClip and PoliceClip
Commit: 319ebaf47c48a73297f84f8d31413d375d968ffa
https://github.com/scummvm/scummvm/commit/319ebaf47c48a73297f84f8d31413d375d968ffa
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2025-12-09T10:59:53+01:00
Commit Message:
PRIVATE: Fully implement AMRadioClip and PoliceClip
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 6ed02a06642..2c977d1d60e 100644
--- a/engines/private/funcs.cpp
+++ b/engines/private/funcs.cpp
@@ -660,34 +660,64 @@ static void fMaskDrawn(ArgArray args) {
_fMask(args, true);
}
-static void fAddSound(Common::String sound, const char *t, Symbol *flag = nullptr, int val = 0) {
- if (sound == "\"\"")
+static void fAMRadioClip(ArgArray args) {
+ assert(args.size() <= 4);
+ debugC(1, kPrivateDebugScript, "AMRadioClip(%s,%d,...)", args[0].u.str, args[1].u.val);
+
+ const char *name = args[0].u.str;
+ if (strcmp(name, "\"\"") == 0) {
+ int clipCount = args[1].u.val;
+ g_private->initializeAMRadioChannels(clipCount);
return;
+ }
- if (strcmp(t, "AMRadioClip") == 0)
- g_private->_AMRadio.push_back(sound);
- else if (strcmp(t, "PoliceClip") == 0)
- g_private->_policeRadio.push_back(sound);
- else
- error("error: invalid sound type %s", t);
-}
+ int priority = args[1].u.val;
-static void fAMRadioClip(ArgArray args) {
- assert(args.size() <= 4);
- fAddSound(args[0].u.str, "AMRadioClip");
+ // The third and fourth parameters are numbers followed by an optional '+' character.
+ // Each number is a priority and the '+' indicates that it is to be treated as a range
+ // instead of the default behavior of requiring an exact match.
+ int disabledPriority1 = (args.size() >= 3) ? args[2].u.val : 0;
+ bool exactPriorityMatch1 = (args.size() >= 3) ? (args[2].type != NUM_PLUS) : true;
+ int disabledPriority2 = (args.size() >= 4) ? args[3].u.val : 0;
+ bool exactPriorityMatch2 = (args.size() >= 4) ? (args[3].type != NUM_PLUS) : true;
+
+ Common::String flagName = (args.size() >= 6) ? *(args[4].u.sym->name) : "";
+ int flagValue = (args.size() >= 6) ? args[5].u.val : 0;
+
+ g_private->addRadioClip(g_private->_AMRadio, name, priority,
+ disabledPriority1, exactPriorityMatch1,
+ disabledPriority2, exactPriorityMatch2,
+ flagName, flagValue);
}
static void fPoliceClip(ArgArray args) {
assert(args.size() <= 4 || args.size() == 6);
- fAddSound(args[0].u.str, "PoliceClip");
- // In the original, the variable is updated when the clip is played, but here we just update
- // the variable when the clip is added to play. The effect for the player, is mostly the same.
- if (args.size() == 6) {
- assert(args[4].type == NAME);
- assert(args[5].type == NUM);
- Symbol *flag = g_private->maps.lookupVariable(args[4].u.sym->name);
- setSymbol(flag, args[5].u.val);
+ debugC(1, kPrivateDebugScript, "PoliceClip(%s,%d,...)", args[0].u.str, args[1].u.val);
+
+ const char *name = args[0].u.str;
+ if (strcmp(name, "\"\"") == 0) {
+ g_private->initializePoliceRadioChannels();
+ return;
}
+
+ int priority = args[1].u.val;
+ if (strcmp(name, "\"DISABLE_ONLY\"") == 0) {
+ g_private->disableRadioClips(g_private->_policeRadio, priority);
+ return;
+ }
+
+ // The third and fourth parameters are numbers followed by an optional '+' character.
+ // Each number is a priority and the '+' indicates that it is to be treated as a range
+ // instead of the default behavior of requiring an exact match.
+ int disabledPriority1 = (args.size() >= 3) ? args[2].u.val : 0;
+ bool exactPriorityMatch1 = (args.size() >= 3) ? (args[2].type != NUM_PLUS) : true;
+ int disabledPriority2 = (args.size() >= 4) ? args[3].u.val : 0;
+ bool exactPriorityMatch2 = (args.size() >= 4) ? (args[3].type != NUM_PLUS) : true;
+
+ g_private->addRadioClip(g_private->_policeRadio, name, priority,
+ disabledPriority1, exactPriorityMatch1,
+ disabledPriority2, exactPriorityMatch2,
+ "", 0);
}
static void fPhoneClip(ArgArray args) {
diff --git a/engines/private/private.cpp b/engines/private/private.cpp
index b83cffb76c0..be54706921e 100644
--- a/engines/private/private.cpp
+++ b/engines/private/private.cpp
@@ -98,8 +98,8 @@ PrivateEngine::PrivateEngine(OSystem *syst, const ADGameDescription *gd)
_policeRadioArea.clear();
_AMRadioArea.clear();
_phoneArea.clear();
- // TODO: use this as a default sound for radio
- _infaceRadioPath = "inface/radio/";
+ _AMRadio.path = "inface/radio/comm_/";
+ _policeRadio.path = "inface/radio/police/";
_phonePrefix = "inface/telephon/";
_phoneCallSound = "phone.wav";
@@ -1253,13 +1253,8 @@ void PrivateEngine::selectAMRadioArea(Common::Point mousePos) {
if (_AMRadioArea.surf == nullptr)
return;
- if (_AMRadio.empty())
- return;
-
if (inMask(_AMRadioArea.surf, mousePos)) {
- Common::String sound = _infaceRadioPath + "comm_/" + _AMRadio.back() + ".wav";
- playSound(sound, 1, false, false);
- _AMRadio.pop_back();
+ playRadio(_AMRadio, false);
}
}
@@ -1267,13 +1262,8 @@ void PrivateEngine::selectPoliceRadioArea(Common::Point mousePos) {
if (_policeRadioArea.surf == nullptr)
return;
- if (_policeRadio.empty())
- return;
-
if (inMask(_policeRadioArea.surf, mousePos)) {
- Common::String sound = _infaceRadioPath + "police/" + _policeRadio.back() + ".wav";
- playSound(sound, 1, false, false);
- _policeRadio.pop_back();
+ playRadio(_policeRadio, true);
}
}
@@ -1394,6 +1384,222 @@ bool PrivateEngine::selectDossierPrevSuspect(Common::Point mousePos) {
return false;
}
+void PrivateEngine::addRadioClip(
+ Radio &radio, const Common::String &name, int priority,
+ int disabledPriority1, bool exactPriorityMatch1,
+ int disabledPriority2, bool exactPriorityMatch2,
+ const Common::String &flagName, int flagValue) {
+
+ // lookup radio clip by name
+ RadioClip *clip = nullptr;
+ for (uint i = 0; i < radio.clips.size(); i++) {
+ if (radio.clips[i].name == name) {
+ clip = &radio.clips[i];
+ break;
+ }
+ }
+
+ // add clip if new
+ if (clip == nullptr) {
+ RadioClip newClip;
+ newClip.name = name;
+ newClip.played = false;
+ newClip.priority = priority;
+ newClip.disabledPriority1 = disabledPriority1;
+ newClip.exactPriorityMatch1 = exactPriorityMatch1;
+ newClip.disabledPriority2 = disabledPriority2;
+ newClip.exactPriorityMatch2 = exactPriorityMatch2;
+ newClip.flagName = flagName;
+ newClip.flagValue = flagValue;
+ radio.clips.push_back(newClip);
+ clip = &radio.clips[radio.clips.size() - 1];
+ }
+
+ // disable other clips based on the clip's priority
+ disableRadioClips(radio, clip->priority);
+}
+
+void PrivateEngine::initializeAMRadioChannels(uint clipCount) {
+ Radio &radio = _AMRadio;
+ assert(clipCount < radio.clips.size());
+
+ // clear all channels
+ for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+ radio.channels[i] = -1;
+ }
+
+ // build array of playable clip indexes (up to clipCount)
+ Common::Array<uint> playableClips;
+ for (uint i = 0; i < clipCount; i++) {
+ if (!radio.clips[i].played) {
+ playableClips.push_back(i);
+ }
+ }
+
+ // place the highest priority clips in the channels (up to two)
+ uint channelCount;
+ switch (playableClips.size()) {
+ case 0: channelCount = 0; break;
+ case 1: channelCount = 1; break;
+ case 2: channelCount = 1; break;
+ case 3: channelCount = 1; break;
+ default: channelCount = 2; break;
+ }
+ uint channel = 0;
+ uint end = 0;
+ while (channel < channelCount) {
+ channel++;
+ if (channel < playableClips.size()) {
+ uint start = channel;
+ uint remainingClips = playableClips.size() - start;
+ while (remainingClips--) {
+ RadioClip &clip1 = radio.clips[playableClips[start]];
+ RadioClip &clip2 = radio.clips[playableClips[end]];
+ if (clip1.priority < clip2.priority) {
+ SWAP(playableClips[start], playableClips[end]);
+ }
+ start++;
+ }
+ }
+ radio.channels[channel - 1] = playableClips[end];
+ end++;
+ }
+
+ // build another array of playable clip indexes, starting at clipCount
+ Common::Array<uint> morePlayableClips;
+ for (uint i = clipCount; i < radio.clips.size(); i++) {
+ if (!radio.clips[i].played) {
+ morePlayableClips.push_back(i);
+ }
+ }
+
+ // shuffle second array
+ if (!morePlayableClips.empty()) {
+ for (uint i = morePlayableClips.size() - 1; i > 0; i--) {
+ uint n = _rnd->getRandomNumber(i);
+ SWAP(morePlayableClips[i], morePlayableClips[n]);
+ }
+ }
+
+ // install some of the clips from the second array into channels, starting
+ // at the end of the channel array to keep the highest priority clips.
+ uint copyCount = morePlayableClips.size();
+ if (playableClips.size() <= 3) { // not morePlayableClips
+ copyCount = MIN<uint>(copyCount, 2);
+ } else {
+ copyCount = MIN<uint>(copyCount, 1);
+ }
+ for (uint i = 0; i < copyCount; i++) {
+ radio.channels[2 - i] = morePlayableClips[i];
+ }
+
+ // shuffle channels
+ for (uint i = ARRAYSIZE(radio.channels) - 1; i > 0; i--) {
+ uint n = _rnd->getRandomNumber(i);
+ SWAP(radio.channels[i], radio.channels[n]);
+ }
+}
+
+void PrivateEngine::initializePoliceRadioChannels() {
+ Radio &radio = _policeRadio;
+
+ // clear all channels
+ for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+ radio.channels[i] = -1;
+ }
+
+ // build array of playable clip indexes
+ Common::Array<uint> playableClips;
+ for (uint i = 0; i < radio.clips.size(); i++) {
+ if (!radio.clips[i].played) {
+ playableClips.push_back(i);
+ }
+ }
+
+ // place the highest priority clips in the channels (up to three)
+ uint channelCount = MIN<uint>(playableClips.size(), ARRAYSIZE(radio.channels));
+ uint channel = 0;
+ uint end = 0;
+ while (channel < channelCount) {
+ channel++;
+ if (channel < playableClips.size()) {
+ uint start = channel;
+ uint remainingClips = playableClips.size() - start;
+ while (remainingClips--) {
+ RadioClip &clip1 = radio.clips[playableClips[start]];
+ RadioClip &clip2 = radio.clips[playableClips[end]];
+ if (clip1.priority < clip2.priority) {
+ SWAP(playableClips[start], playableClips[end]);
+ }
+ start++;
+ }
+ }
+ radio.channels[channel - 1] = playableClips[end];
+ end++;
+ }
+}
+
+void PrivateEngine::disableRadioClips(Radio &radio, int priority) {
+ for (uint i = 0; i < radio.clips.size(); i++) {
+ RadioClip &clip = radio.clips[i];
+ if (clip.played) {
+ continue;
+ }
+
+ if (clip.disabledPriority1) {
+ if ((clip.exactPriorityMatch1 && priority == clip.disabledPriority1) ||
+ (!clip.exactPriorityMatch1 && priority <= clip.disabledPriority1)) {
+ clip.played = true;
+ }
+ }
+ if (clip.disabledPriority2) {
+ if ((clip.exactPriorityMatch2 && priority == clip.disabledPriority2) ||
+ (!clip.exactPriorityMatch2 && priority <= clip.disabledPriority2)) {
+ clip.played = true;
+ }
+ }
+ }
+}
+
+void PrivateEngine::playRadio(Radio &radio, bool randomlyDisableClips) {
+ // search channels for first available clip
+ for (uint i = 0; i < ARRAYSIZE(radio.channels); i++) {
+ // skip empty channels
+ if (radio.channels[i] == -1) {
+ continue;
+ }
+
+ // verify that clip hasn't been already been played
+ RadioClip &clip = radio.clips[radio.channels[i]];
+ radio.channels[i] = -1;
+ if (clip.played) {
+ continue;
+ }
+
+ // the police radio randomly disables clips (!)
+ if (randomlyDisableClips) {
+ uint r = _rnd->getRandomNumber(9);
+ if (r < 3) {
+ clip.played = true;
+ break; // play radio.wav
+ }
+ }
+
+ // play the clip
+ Common::String sound = radio.path + clip.name + ".wav";
+ playSound(sound, 1, false, false);
+ clip.played = true;
+ if (!clip.flagName.empty()) {
+ Symbol *flag = maps.lookupVariable(&(clip.flagName));
+ setSymbol(flag, clip.flagValue);
+ }
+ return;
+ }
+
+ // play default radio sound
+ playSound("inface/radio/radio.wav", 1, false, 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;
@@ -1785,18 +1991,28 @@ Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream)
_policeBustPreviousSetting = stream->readString();
// Radios
- size = stream->readUint32LE();
- _AMRadio.clear();
-
- for (uint32 i = 0; i < size; ++i) {
- _AMRadio.push_back(stream->readString());
- }
-
- size = stream->readUint32LE();
- _policeRadio.clear();
-
- for (uint32 i = 0; i < size; ++i) {
- _policeRadio.push_back(stream->readString());
+ Radio *radios[] = { &_AMRadio, &_policeRadio };
+ for (uint r = 0; r < ARRAYSIZE(radios); r++) {
+ Radio *radio = radios[r];
+ radio->clear();
+
+ size = stream->readUint32LE();
+ for (uint32 i = 0; i < size; ++i) {
+ RadioClip clip;
+ clip.name = stream->readString();
+ clip.played = (stream->readByte() == 1);
+ clip.priority = stream->readSint32LE();
+ clip.disabledPriority1 = stream->readSint32LE();
+ clip.exactPriorityMatch1 = (stream->readByte() == 1);
+ clip.disabledPriority2 = stream->readSint32LE();
+ clip.exactPriorityMatch2 = (stream->readByte() == 1);
+ clip.flagName = stream->readString();
+ clip.flagValue = stream->readSint32LE();
+ radio->clips.push_back(clip);
+ }
+ for (uint i = 0; i < ARRAYSIZE(radio->channels); i++) {
+ radio->channels[i] = stream->readSint32LE();
+ }
}
size = stream->readUint32LE();
@@ -1925,15 +2141,27 @@ Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool is
stream->writeByte(0);
// Radios
- stream->writeUint32LE(_AMRadio.size());
- for (SoundList::const_iterator it = _AMRadio.begin(); it != _AMRadio.end(); ++it) {
- stream->writeString(*it);
- stream->writeByte(0);
- }
- stream->writeUint32LE(_policeRadio.size());
- for (SoundList::const_iterator it = _policeRadio.begin(); it != _policeRadio.end(); ++it) {
- stream->writeString(*it);
- stream->writeByte(0);
+ Radio *radios[] = { &_AMRadio, &_policeRadio };
+ for (uint r = 0; r < ARRAYSIZE(radios); r++) {
+ Radio *radio = radios[r];
+ stream->writeUint32LE(radio->clips.size());
+ for (uint i = 0; i < radio->clips.size(); i++) {
+ RadioClip &clip = radio->clips[i];
+ stream->writeString(clip.name);
+ stream->writeByte(0);
+ stream->writeByte(clip.played ? 1 : 0);
+ stream->writeSint32LE(clip.priority);
+ stream->writeSint32LE(clip.disabledPriority1);
+ stream->writeByte(clip.exactPriorityMatch1 ? 1 : 0);
+ stream->writeSint32LE(clip.disabledPriority2);
+ stream->writeByte(clip.exactPriorityMatch2 ? 1 : 0);
+ stream->writeString(clip.flagName);
+ stream->writeByte(0);
+ stream->writeSint32LE(clip.flagValue);
+ }
+ for (uint i = 0; i < ARRAYSIZE(radio->channels); i++) {
+ stream->writeSint32LE(radio->channels[i]);
+ }
}
// Phone
diff --git a/engines/private/private.h b/engines/private/private.h
index b1261a2878b..41debcccf7f 100644
--- a/engines/private/private.h
+++ b/engines/private/private.h
@@ -139,6 +139,35 @@ typedef struct PhoneInfo {
Common::Array<Common::String> sounds;
} PhoneInfo;
+typedef struct RadioClip {
+ Common::String name;
+ bool played;
+ int priority;
+ int disabledPriority1; // 0 == none
+ bool exactPriorityMatch1;
+ int disabledPriority2; // 0 == none
+ bool exactPriorityMatch2;
+ Common::String flagName;
+ int flagValue;
+} RadioClip;
+
+typedef struct Radio {
+ Common::String path;
+ Common::Array<RadioClip> clips;
+ int channels[3];
+
+ Radio() {
+ clear();
+ }
+
+ void clear() {
+ clips.clear();
+ for (uint i = 0; i < ARRAYSIZE(channels); i++) {
+ channels[i] = -1;
+ }
+ }
+} Radio;
+
typedef struct DossierInfo {
Common::String page1;
Common::String page2;
@@ -181,7 +210,6 @@ extern const FuncTable funcTable[];
typedef Common::List<ExitInfo> ExitList;
typedef Common::List<MaskInfo> MaskList;
-typedef Common::List<Common::String> SoundList;
typedef Common::List<PhoneInfo> PhoneList;
typedef Common::List<InventoryItem> InvList;
typedef Common::List<Common::Rect *> RectList;
@@ -426,11 +454,19 @@ public:
Common::String _sirenSound;
// Radios
- Common::String _infaceRadioPath;
MaskInfo _AMRadioArea;
MaskInfo _policeRadioArea;
- SoundList _AMRadio;
- SoundList _policeRadio;
+ Radio _AMRadio;
+ Radio _policeRadio;
+ void addRadioClip(
+ Radio &radio, const Common::String &name, int priority,
+ int disabledPriority1, bool exactPriorityMatch1,
+ int disabledPriority2, bool exactPriorityMatch2,
+ const Common::String &flagName, int flagValue);
+ void initializeAMRadioChannels(uint clipCount);
+ void initializePoliceRadioChannels();
+ void disableRadioClips(Radio &radio, int priority);
+ void playRadio(Radio &radio, bool randomlyDisableClips);
void selectAMRadioArea(Common::Point);
void selectPoliceRadioArea(Common::Point);
diff --git a/engines/private/savegame.h b/engines/private/savegame.h
index ecf2f291873..f606d9fb823 100644
--- a/engines/private/savegame.h
+++ b/engines/private/savegame.h
@@ -36,13 +36,14 @@ namespace Private {
//
// Version - new/changed feature
// =============================
+// 3 - Radio detailed state (December 2025)
// 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 = 2;
-const uint16 kMinimumSavegameVersion = 2;
+const uint16 kCurrentSavegameVersion = 3;
+const uint16 kMinimumSavegameVersion = 3;
struct SavegameMetadata {
uint16 version;
More information about the Scummvm-git-logs
mailing list