[Scummvm-git-logs] scummvm master -> 1ac66919f9960e7fe98b57d065667c66b80a184b
bluegr
noreply at scummvm.org
Mon Jun 29 23:53:49 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
8d56ce65ee NANCY: NANCY12: Implement MindPuzzle
1ac66919f9 NANCY: More work on random sound playing
Commit: 8d56ce65ee4ada3fd2b6846cbecbe72adff392a5
https://github.com/scummvm/scummvm/commit/8d56ce65ee4ada3fd2b6846cbecbe72adff392a5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-30T02:53:40+03:00
Commit Message:
NANCY: NANCY12: Implement MindPuzzle
This is a Mastermind clone. The player guesses a hidden color code over a
number of rows, by placing colored balls in each row. Each submitted guess
is scored with flag poles (right color, right slot) and plain poles (right
color, wrong slot).
Changed paths:
A engines/nancy/action/puzzle/mindpuzzle.cpp
A engines/nancy/action/puzzle/mindpuzzle.h
engines/nancy/action/arfactory.cpp
engines/nancy/commontypes.cpp
engines/nancy/commontypes.h
engines/nancy/module.mk
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 75f3c6b709a..8d508efe0bc 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -51,6 +51,7 @@
#include "engines/nancy/action/puzzle/magnetmazepuzzle.h"
#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
#include "engines/nancy/action/puzzle/memorypuzzle.h"
+#include "engines/nancy/action/puzzle/mindpuzzle.h"
#include "engines/nancy/action/puzzle/mouselightpuzzle.h"
#include "engines/nancy/action/puzzle/multibuildpuzzle.h"
#include "engines/nancy/action/puzzle/onebuildpuzzle.h"
@@ -453,8 +454,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
// TODO: Nancy12 BoardGamePuzzle (new), not implemented
return nullptr;
case 165:
- // TODO: Nancy12 MindPuzzle (new), not implemented
- return nullptr;
+ return new MindPuzzle();
case 166:
// OneBuildPuzzle, moved here from 234 in Nancy12.
// TODO: verify the Nancy12 data layout against OneBuildPuzzle::readData
diff --git a/engines/nancy/action/puzzle/mindpuzzle.cpp b/engines/nancy/action/puzzle/mindpuzzle.cpp
new file mode 100644
index 00000000000..f66e0f38515
--- /dev/null
+++ b/engines/nancy/action/puzzle/mindpuzzle.cpp
@@ -0,0 +1,396 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/random.h"
+
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/resource.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+
+#include "engines/nancy/state/scene.h"
+#include "engines/nancy/action/puzzle/mindpuzzle.h"
+
+namespace Nancy {
+namespace Action {
+
+void MindPuzzle::readData(Common::SeekableReadStream &stream) {
+ // 608-byte base header (fixed-size fields; the color palette is a fixed-size
+ // array and the count fields sit at the end).
+ readFilename(stream, _imageName); // 0x00, 33 bytes
+
+ stream.skip(0x25 - 0x21); // 0x21: two count fields
+ readRect(stream, _exitHotspot); // 0x25: give-up / leave hotspot
+ _winScene.readData(stream); // 0x35: scene reached on a solved code (25 bytes)
+ _numGuesses = stream.readUint16LE(); // 0x4e
+
+ _loseScene.readData(stream); // 0x50: out-of-guesses / exit scene (25 bytes)
+ stream.skip(0x79 - 0x69); // 0x69: unused rect
+ stream.skip(16); // 0x79: tall right-edge rect (answer-reveal area), not a hotspot
+
+ stream.skip(0xd9 - 0x89); // answer-reveal positions (unused)
+ readRect(stream, _feedbackSrcRects[0]); // 0xd9, right-color-wrong-slot peg (plain pole)
+ readRect(stream, _feedbackSrcRects[1]); // 0xe9, right-color-and-slot peg (flag pole)
+
+ // The golf-club submit button's screen rect (the actual hotspot) is at 0x109;
+ // the rect at 0x79 used previously sat above the button, so clicks missed it.
+ stream.skip(0x109 - 0xf9); // 0xf9: another (unused) sprite rect
+ readRect(stream, _submitButtonRect); // 0x109
+
+ readRectArray(stream, _ballSrcRects, kMaxColors); // 0x119, ball sprite sources
+ readRectArray(stream, _ballHitRects, kMaxColors); // 0x1b9, bottom-row ball positions
+
+ _allowDuplicates = stream.readByte(); // 0x259
+ _numColors = stream.readUint16LE(); // 0x25a
+ _codeLength = stream.readUint16LE(); // 0x25c
+ _numRows = stream.readUint16LE(); // 0x25e
+
+ _ballSrcRects.resize(_numColors);
+ _ballHitRects.resize(_numColors);
+
+ // Row records - 10 slot rects each (guess pegs 0..codeLength-1, feedback pegs 5..).
+ _rows.resize(_numRows);
+ for (uint r = 0; r < _numRows; ++r) {
+ for (int s = 0; s < 10; ++s) {
+ readRect(stream, _rows[r].slotRects[s]);
+ }
+ }
+
+ for (uint i = 0; i < kNumSounds; ++i) {
+ _sounds[i].readData(stream);
+ }
+}
+
+void MindPuzzle::generateSecret() {
+ _secret.resize(_codeLength);
+ _secretColorCount.clear();
+ _secretColorCount.resize(_numColors, 0);
+
+ for (uint i = 0; i < _codeLength; ++i) {
+ int16 color;
+ if (_allowDuplicates == 0) {
+ // Reject colors already used in the code.
+ bool dup;
+ do {
+ dup = false;
+ color = g_nancy->_randomSource->getRandomNumber(_numColors - 1);
+ for (uint j = 0; j < i; ++j) {
+ if (_secret[j] == color) {
+ dup = true;
+ break;
+ }
+ }
+ } while (dup);
+ } else {
+ color = g_nancy->_randomSource->getRandomNumber(_numColors - 1);
+ }
+ _secret[i] = color;
+ ++_secretColorCount[color];
+ }
+}
+
+void MindPuzzle::scoreRow(int row) {
+ // Standard Mastermind scoring with duplicate-aware white pegs.
+ Common::Array<int16> matched(_numColors, 0);
+
+ // Black pegs: exact color + position.
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_guess[row][s] == _secret[s]) {
+ ++matched[_secret[s]];
+ }
+ }
+
+ // White pegs: right color in a wrong slot, limited by how many of that color
+ // remain unmatched in the code.
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_guess[row][s] == _secret[s]) {
+ continue;
+ }
+ int16 color = _guess[row][s];
+ for (uint t = 0; t < _codeLength; ++t) {
+ if (_secret[t] == color && _guess[row][t] != _secret[t]) {
+ if (matched[color] < _secretColorCount[color]) {
+ ++matched[color];
+ }
+ break;
+ }
+ }
+ }
+
+ // Lay out the feedback pegs: blacks first, then whites, then empties.
+ int blacks = 0;
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_guess[row][s] == _secret[s]) {
+ ++blacks;
+ }
+ }
+ int whites = 0;
+ for (int16 c = 0; c < (int16)_numColors; ++c) {
+ whites += matched[c];
+ }
+ whites -= blacks;
+
+ for (uint s = 0; s < _codeLength; ++s) {
+ if ((int)s < blacks) {
+ _feedback[row][s] = kFeedbackBlack;
+ } else if ((int)s < blacks + whites) {
+ _feedback[row][s] = kFeedbackWhite;
+ } else {
+ _feedback[row][s] = kFeedbackNone;
+ }
+ }
+
+ if (blacks == (int)_codeLength) {
+ _solved = true;
+ }
+}
+
+void MindPuzzle::drawPeg(int color, const Common::Rect &dest) {
+ if (color < 0 || color >= (int)_ballSrcRects.size() || dest.isEmpty()) {
+ return;
+ }
+ _drawSurface.blitFrom(_image, _ballSrcRects[color], dest);
+}
+
+void MindPuzzle::redraw() {
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+
+ for (uint r = 0; r < _rows.size(); ++r) {
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_guess[r][s] >= 0) {
+ drawPeg(_guess[r][s], _rows[r].slotRects[s]);
+ }
+ // Feedback pegs sit in the second half of the row's slots: a plain pole
+ // for a right color in the wrong slot, a flag pole for a right color + slot.
+ if (_feedback[r][s] != kFeedbackNone) {
+ const Common::Rect &dest = _rows[r].slotRects[5 + s];
+ if (!dest.isEmpty()) {
+ _drawSurface.blitFrom(_image, _feedbackSrcRects[_feedback[r][s] == kFeedbackBlack ? 1 : 0], dest);
+ }
+ }
+ }
+ }
+
+ // The ball being dragged follows the cursor, centered on it.
+ if (_heldColor >= 0 && _heldColor < (int)_ballSrcRects.size()) {
+ const Common::Rect &src = _ballSrcRects[_heldColor];
+ int w = src.width();
+ int h = src.height();
+ Common::Rect dst(_heldDrawPos.x - w / 2, _heldDrawPos.y - h / 2,
+ _heldDrawPos.x - w / 2 + w, _heldDrawPos.y - h / 2 + h);
+ _drawSurface.blitFrom(_image, src, dst);
+ }
+
+ _needsRedraw = true;
+}
+
+int MindPuzzle::paletteHit(const Common::Point &mouseVP) const {
+ for (uint i = 0; i < _ballHitRects.size(); ++i) {
+ if (_ballHitRects[i].contains(mouseVP)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+bool MindPuzzle::slotHit(const Common::Point &mouseVP, int &slot) const {
+ if (_currentRow >= (int)_rows.size()) {
+ return false;
+ }
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_rows[_currentRow].slotRects[s].contains(mouseVP)) {
+ slot = s;
+ return true;
+ }
+ }
+ return false;
+}
+
+void MindPuzzle::init() {
+ Common::Rect vpBounds = NancySceneState.getViewport().getBounds();
+ _drawSurface.create(vpBounds.width(), vpBounds.height(),
+ g_nancy->_graphics->getInputPixelFormat());
+ _drawSurface.clear(g_nancy->_graphics->getTransColor());
+ setTransparent(true);
+ setVisible(true);
+ moveTo(vpBounds);
+
+ g_nancy->_resource->loadImage(_imageName, _image);
+ _image.setTransparentColor(_drawSurface.getTransparentColor());
+
+ for (uint r = 0; r < kMaxRows; ++r) {
+ for (uint s = 0; s < kSlotsPerRow; ++s) {
+ _guess[r][s] = -1;
+ _feedback[r][s] = kFeedbackNone;
+ }
+ }
+
+ _currentRow = 0;
+ _heldColor = -1;
+ _remainingGuesses = _numGuesses;
+ _solved = false;
+
+ generateSecret();
+ redraw();
+}
+
+void MindPuzzle::execute() {
+ switch (_state) {
+ case kBegin:
+ init();
+ registerGraphics();
+ _state = kRun;
+ // fall through
+ case kRun:
+ if (_solved) {
+ _state = kActionTrigger;
+ }
+ break;
+ case kActionTrigger:
+ if (_solved) {
+ // Win: play the applause cue, wait for it to finish, then move to the win scene.
+ if (!_outcomeStarted) {
+ _outcomeStarted = true;
+ const RandomSoundBlock &applause = _sounds[kApplauseSound];
+ if (!applause.names.empty()) {
+ _outcomeSound.name = applause.names[g_nancy->_randomSource->getRandomNumber(applause.names.size() - 1)];
+ _outcomeSound.channelID = applause.channel;
+ _outcomeSound.numLoops = applause.numLoops;
+ _outcomeSound.volume = applause.volume;
+ g_nancy->_sound->loadSound(_outcomeSound);
+ g_nancy->_sound->playSound(_outcomeSound);
+ }
+ }
+
+ if (g_nancy->_sound->isSoundPlaying(_outcomeSound)) {
+ return;
+ }
+
+ g_nancy->_sound->stopSound(_outcomeSound);
+ NancySceneState.setEventFlag(_winScene._flag);
+ NancySceneState.changeScene(_winScene._sceneChange);
+ } else {
+ // Out of guesses, or the player left via the exit hotspot.
+ NancySceneState.setEventFlag(_loseScene._flag);
+ NancySceneState.changeScene(_loseScene._sceneChange);
+ }
+
+ finishExecution();
+ break;
+ }
+}
+
+void MindPuzzle::handleInput(NancyInput &input) {
+ if (_state != kRun || _solved) {
+ return;
+ }
+
+ Common::Rect vpScreen = NancySceneState.getViewport().getScreenPosition();
+ Common::Point mouseVP = input.mousePos - Common::Point(vpScreen.left, vpScreen.top);
+
+ // A held ball follows the cursor; release it into a slot (swapping with any
+ // ball already there) or anywhere else to send it back to the palette.
+ if (_heldColor != -1) {
+ g_nancy->_cursor->setCursorType(CursorManager::kDragHand);
+
+ if (_heldDrawPos != mouseVP) {
+ _heldDrawPos = mouseVP;
+ redraw();
+ }
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ int slot = -1;
+ if (slotHit(mouseVP, slot)) {
+ int16 previous = _guess[_currentRow][slot];
+ _guess[_currentRow][slot] = _heldColor;
+ _heldColor = previous; // keep dragging the displaced ball, if any
+ } else {
+ _heldColor = -1;
+ }
+ redraw();
+ }
+ return;
+ }
+
+ // Pick up a ball from the bottom palette.
+ int color = paletteHit(mouseVP);
+ if (color != -1) {
+ g_nancy->_cursor->setCursorType(CursorManager::kDragHand);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _heldColor = color;
+ _heldDrawPos = mouseVP;
+ redraw();
+ }
+ return;
+ }
+
+ // Pick a ball back up from a filled slot in the current row.
+ int slot = -1;
+ if (slotHit(mouseVP, slot) && _guess[_currentRow][slot] != -1) {
+ g_nancy->_cursor->setCursorType(CursorManager::kDragHand);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _heldColor = _guess[_currentRow][slot];
+ _guess[_currentRow][slot] = -1;
+ _heldDrawPos = mouseVP;
+ redraw();
+ }
+ return;
+ }
+
+ // Submit the row via the golf-club button, once every slot is filled.
+ if (_submitButtonRect.contains(mouseVP)) {
+ bool full = true;
+ for (uint s = 0; s < _codeLength; ++s) {
+ if (_guess[_currentRow][s] < 0) {
+ full = false;
+ break;
+ }
+ }
+
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+ if (full && (input.input & NancyInput::kLeftMouseButtonUp)) {
+ scoreRow(_currentRow);
+ redraw();
+ if (!_solved) {
+ ++_currentRow;
+ --_remainingGuesses;
+ if (_currentRow >= (int)_rows.size() || _remainingGuesses <= 0) {
+ // Out of guesses.
+ _state = kActionTrigger;
+ }
+ }
+ }
+ return;
+ }
+
+ // Leave the puzzle (give up) via the exit hotspot; this reports a loss.
+ if (_exitHotspot.contains(mouseVP)) {
+ g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _state = kActionTrigger;
+ }
+ }
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/mindpuzzle.h b/engines/nancy/action/puzzle/mindpuzzle.h
new file mode 100644
index 00000000000..c06e1c9c198
--- /dev/null
+++ b/engines/nancy/action/puzzle/mindpuzzle.h
@@ -0,0 +1,114 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_MINDPUZZLE_H
+#define NANCY_ACTION_MINDPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+#include "engines/nancy/commontypes.h"
+
+namespace Nancy {
+namespace Action {
+
+// Mastermind clone introduced in Nancy12 (AR 165). The player guesses a hidden
+// color code over a number of rows, by placing colored balls in each row. Each
+// submitted guess is scored with flag poles (right color, right slot) and plain
+// poles (right color, wrong slot).
+class MindPuzzle : public RenderActionRecord {
+public:
+ MindPuzzle() : RenderActionRecord(7) {}
+ virtual ~MindPuzzle() {}
+
+ void init() override;
+
+ void readData(Common::SeekableReadStream &stream) override;
+ void execute() override;
+ void handleInput(NancyInput &input) override;
+
+ bool isViewportRelative() const override { return true; }
+
+ static const uint kNumSounds = 6;
+ static const uint kApplauseSound = 4; // index into _sounds: the win cue
+ static const uint kMaxRows = 10;
+ static const uint kMaxColors = 10; // fixed palette array size in the data
+ static const uint kSlotsPerRow = 5; // physical slots per row record (code length <= this)
+
+ enum Feedback { kFeedbackNone = -1, kFeedbackWhite = 0, kFeedbackBlack = 1 };
+
+protected:
+ Common::String getRecordTypeName() const override { return "MindPuzzle"; }
+
+ struct Row {
+ // Per-slot dest rects: [0..codeLength) guess pegs, [5..5+codeLength) feedback pegs.
+ Common::Rect slotRects[10];
+ };
+
+ // File data
+ Common::Path _imageName;
+
+ uint16 _numColors = 0;
+ uint16 _codeLength = 0;
+ uint16 _numRows = 0;
+ byte _allowDuplicates = 0;
+ uint16 _numGuesses = 0;
+
+ Common::Array<Common::Rect> _ballSrcRects; // each color's ball sprite in the overlay image (blit source)
+ Common::Array<Common::Rect> _ballHitRects; // bottom-row clickable ball positions, one per color
+ Common::Rect _feedbackSrcRects[2]; // [0] right color, wrong slot (plain pole); [1] right color + slot (flag pole)
+ Common::Rect _submitButtonRect; // golf-club button: submit the current row
+ Common::Rect _exitHotspot; // give-up / leave hotspot
+ Common::Array<Row> _rows;
+
+ SceneChangeWithFlag _winScene; // reached on a solved code
+ SceneChangeWithFlag _loseScene; // reached when out of guesses or when leaving via the exit hotspot
+
+ RandomSoundBlock _sounds[kNumSounds]; // click / wall / wood / ball / applause / coin
+
+ // Runtime state
+ Common::Array<int16> _secret; // _codeLength entries (color indices)
+ Common::Array<int16> _secretColorCount;
+
+ int16 _guess[kMaxRows][kSlotsPerRow];
+ int16 _feedback[kMaxRows][kSlotsPerRow];
+
+ int16 _currentRow = 0;
+ int16 _heldColor = -1;
+ Common::Point _heldDrawPos; // viewport-local cursor while a ball is held
+ int16 _remainingGuesses = 0;
+ bool _solved = false;
+
+ SoundDescription _outcomeSound; // applause cue played once on a win
+ bool _outcomeStarted = false;
+
+ Graphics::ManagedSurface _image;
+
+ void generateSecret();
+ void scoreRow(int row);
+ void drawPeg(int color, const Common::Rect &dest);
+ void redraw();
+ int paletteHit(const Common::Point &mouseVP) const;
+ bool slotHit(const Common::Point &mouseVP, int &slot) const;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_MINDPUZZLE_H
diff --git a/engines/nancy/commontypes.cpp b/engines/nancy/commontypes.cpp
index 0ba4dbe9a98..219a1c0b1da 100644
--- a/engines/nancy/commontypes.cpp
+++ b/engines/nancy/commontypes.cpp
@@ -273,6 +273,19 @@ void SoundDescription::readTerse(Common::SeekableReadStream &stream) {
stream.skip(2);
}
+void RandomSoundBlock::readData(Common::SeekableReadStream &stream) {
+ int16 count = stream.readSint16LE();
+ if (count > 0) {
+ names.resize(count);
+ for (int i = 0; i < count; ++i) {
+ readFilename(stream, names[i]);
+ }
+ channel = stream.readSint16LE();
+ numLoops = stream.readSint32LE();
+ volume = stream.readSint16LE();
+ }
+}
+
void ConditionalDialogue::readData(Common::SeekableReadStream &stream) {
textID = stream.readByte();
sceneID = stream.readUint16LE();
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 18d361e844d..004c3b43387 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -269,6 +269,16 @@ struct SoundDescription {
void readTerse(Common::SeekableReadStream &stream);
};
+// The "random sound" block shared by some Nancy 12 puzzles
+struct RandomSoundBlock {
+ Common::Array<Common::String> names;
+ int16 channel = 0;
+ int32 numLoops = 0;
+ int16 volume = 0;
+
+ void readData(Common::SeekableReadStream &stream);
+};
+
// Structs inside nancy.dat, which contains all the data that was
// originally stored inside the executable
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 4dfa6421f91..1fb0010dfd5 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -34,6 +34,7 @@ MODULE_OBJS = \
action/puzzle/mazechasepuzzle.o \
action/puzzle/matchpuzzle.o \
action/puzzle/memorypuzzle.o \
+ action/puzzle/mindpuzzle.o \
action/puzzle/mouselightpuzzle.o \
action/puzzle/multibuildpuzzle.o \
action/puzzle/onebuildpuzzle.o \
Commit: 1ac66919f9960e7fe98b57d065667c66b80a184b
https://github.com/scummvm/scummvm/commit/1ac66919f9960e7fe98b57d065667c66b80a184b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-30T02:53:42+03:00
Commit Message:
NANCY: More work on random sound playing
- More faithful implementation of PlaySound and PlaySoundTerse -
checked against Nancy 7 through Nancy 12
- Implement forced sound selection, for sounds with a * marker
Changed paths:
engines/nancy/action/soundrecords.cpp
engines/nancy/action/soundrecords.h
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index fefda95c842..7725cfa7b8a 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -331,20 +331,29 @@ void StopSound::execute() {
_sceneChange.execute();
}
+// A name beginning with '*' is the forced selection (the marker is stripped);
+// otherwise the played sound is picked at random. The choice is made once, when
+// the record is loaded.
+static uint selectRandomSound(Common::Array<Common::String> &soundNames) {
+ for (uint i = 0; i < soundNames.size(); ++i) {
+ if (soundNames[i].hasPrefix("*")) {
+ soundNames[i].deleteChar(0);
+ return i;
+ }
+ }
+
+ return g_nancy->_randomSource->getRandomNumber(soundNames.size() - 1);
+}
+
void PlayRandomSound::readData(Common::SeekableReadStream &stream) {
uint16 numSounds = stream.readUint16LE();
readFilenameArray(stream, _soundNames, numSounds - 1);
PlaySound::readData(stream);
_soundNames.push_back(_sound.name);
-}
-void PlayRandomSound::execute() {
- if (_state == kBegin) {
- _sound.name = _soundNames[g_nancy->_randomSource->getRandomNumber(_soundNames.size() - 1)];
- }
-
- PlaySound::execute();
+ _selectedSound = selectRandomSound(_soundNames);
+ _sound.name = _soundNames[_selectedSound];
}
void PlayRandomSoundTerse::readData(Common::SeekableReadStream &stream) {
@@ -361,16 +370,10 @@ void PlayRandomSoundTerse::readData(Common::SeekableReadStream &stream) {
_ccTexts.push_back(Common::String());
readCCText(stream, _ccTexts.back());
}
-}
-
-void PlayRandomSoundTerse::execute() {
- if (_state == kBegin) {
- uint16 randomID = g_nancy->_randomSource->getRandomNumber(_soundNames.size() - 1);
- _sound.name = _soundNames[randomID];
- _ccText = _ccTexts[randomID];
- }
- PlaySoundCC::execute();
+ _selectedSound = selectRandomSound(_soundNames);
+ _sound.name = _soundNames[_selectedSound];
+ _ccText = _ccTexts[_selectedSound];
}
void TableIndexPlaySound::readData(Common::SeekableReadStream &stream) {
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
index b429e84ab85..6679648a775 100644
--- a/engines/nancy/action/soundrecords.h
+++ b/engines/nancy/action/soundrecords.h
@@ -192,12 +192,12 @@ protected:
Common::String getRecordTypeName() const override { return "StopSound"; }
};
-// Same as PlaySound, except it randomly picks between one of several
-// provided sound files; all other settings for the sound are shared.
+// Same as PlaySound, except it randomly picks between one of several provided
+// sound files; all other settings for the sound are shared. The played sound is
+// chosen when the record is loaded.
class PlayRandomSound : public PlaySound {
public:
void readData(Common::SeekableReadStream &stream) override;
- void execute() override;
Common::Array<Common::String> _soundNames;
@@ -207,11 +207,11 @@ protected:
Common::String getRecordTypeName() const override { return "PlayRandomSound"; }
};
-// Short version of PlayRandomSound, but ALSO supports closed captioning text
+// Short version of PlayRandomSound, but ALSO supports closed captioning text.
+// The played sound is chosen at random when the record is loaded.
class PlayRandomSoundTerse : public PlaySoundTerse {
public:
void readData(Common::SeekableReadStream &stream) override;
- void execute() override;
Common::Array<Common::String> _soundNames;
Common::Array<Common::String> _ccTexts;
More information about the Scummvm-git-logs
mailing list