[Scummvm-git-logs] scummvm master -> 93d8dcadcd02a6c25cbb025a0bc8469cf1717c05
fracturehill
noreply at scummvm.org
Tue Aug 22 09:16:44 UTC 2023
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
860f5cd090 NANCY: Add 3D sound support
b6ae5b4cd1 NANCY: Allow chained orFlag dependencies
93d8dcadcd NANCY: Fix ShowInventoryItem in nancy3 and up
Commit: 860f5cd0908d6ab6206df9de14f7b8a8a0d1978e
https://github.com/scummvm/scummvm/commit/860f5cd0908d6ab6206df9de14f7b8a8a0d1978e
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-22T12:15:26+03:00
Commit Message:
NANCY: Add 3D sound support
Added support for 3D sound, as introduced in nancy3.
The original engine used DirectSound 3D, which enabled
for easy sound panning, attenuation based on distance,
and doppler effect simulation. The DS3D algorithm
has only been approximated; in particular, there are some
minor differences when it comes to sound panning.
Changed paths:
engines/nancy/action/recordtypes.cpp
engines/nancy/action/recordtypes.h
engines/nancy/commontypes.cpp
engines/nancy/commontypes.h
engines/nancy/console.cpp
engines/nancy/sound.cpp
engines/nancy/sound.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
diff --git a/engines/nancy/action/recordtypes.cpp b/engines/nancy/action/recordtypes.cpp
index f01f4ef3b4f..dd9612425ea 100644
--- a/engines/nancy/action/recordtypes.cpp
+++ b/engines/nancy/action/recordtypes.cpp
@@ -557,6 +557,12 @@ void ShowInventoryItem::execute() {
void PlayDigiSoundAndDie::readData(Common::SeekableReadStream &stream) {
_sound.readDIGI(stream);
+
+ if (g_nancy->getGameType() >= kGameTypeNancy3) {
+ _soundEffect = new SoundEffectDescription;
+ _soundEffect->readData(stream);
+ }
+
_sceneChange.readData(stream, g_nancy->getGameType() == kGameTypeVampire);
_flagOnTrigger.label = stream.readSint16LE();
@@ -567,7 +573,7 @@ void PlayDigiSoundAndDie::readData(Common::SeekableReadStream &stream) {
void PlayDigiSoundAndDie::execute() {
switch (_state) {
case kBegin:
- g_nancy->_sound->loadSound(_sound);
+ g_nancy->_sound->loadSound(_sound, &_soundEffect);
g_nancy->_sound->playSound(_sound);
_state = kRun;
break;
@@ -593,10 +599,11 @@ void PlayDigiSoundAndDie::execute() {
void PlaySoundPanFrameAnchorAndDie::readData(Common::SeekableReadStream &stream) {
_sound.readDIGI(stream);
stream.skip(2);
+ _sound.isPanning = true;
}
void PlaySoundPanFrameAnchorAndDie::execute() {
- g_nancy->_sound->loadSound(_sound, true);
+ g_nancy->_sound->loadSound(_sound);
g_nancy->_sound->playSound(_sound);
_isDone = true;
}
diff --git a/engines/nancy/action/recordtypes.h b/engines/nancy/action/recordtypes.h
index ac370b917d5..00314353c71 100644
--- a/engines/nancy/action/recordtypes.h
+++ b/engines/nancy/action/recordtypes.h
@@ -409,10 +409,14 @@ protected:
class PlayDigiSoundAndDie : public ActionRecord {
public:
+ PlayDigiSoundAndDie() {}
+ ~PlayDigiSoundAndDie() { delete _soundEffect; }
+
void readData(Common::SeekableReadStream &stream) override;
void execute() override;
SoundDescription _sound;
+ SoundEffectDescription *_soundEffect = nullptr;
SceneChangeDescription _sceneChange;
FlagDescription _flagOnTrigger;
diff --git a/engines/nancy/commontypes.cpp b/engines/nancy/commontypes.cpp
index 65ccbe49ef7..b63bf0b21ea 100644
--- a/engines/nancy/commontypes.cpp
+++ b/engines/nancy/commontypes.cpp
@@ -35,10 +35,15 @@ void SceneChangeDescription::readData(Common::SeekableReadStream &stream, bool l
paletteID = stream.readByte();
stream.skip(2);
}
+
continueSceneSound = stream.readUint16LE();
if (g_nancy->getGameType() >= kGameTypeNancy3) {
- stream.skip(12); // 3D sound listener position
+ int32 x = stream.readSint32LE();
+ int32 y = stream.readSint32LE();
+ int32 z = stream.readSint32LE();
+ listenerFrontVector.set(x, y, z);
+ frontVectorFrameID = frameID;
}
}
@@ -95,6 +100,40 @@ void SecondaryVideoDescription::readData(Common::SeekableReadStream &stream) {
stream.skip(0x20);
}
+void SoundEffectDescription::readData(Common::SeekableReadStream &stream) {
+ minTimeDelay = stream.readUint32LE();
+ maxTimeDelay = stream.readUint32LE();
+
+ randomMoveMinX = stream.readSint32LE();
+ randomMoveMaxX = stream.readSint32LE();
+ randomMoveMinY = stream.readSint32LE();
+ randomMoveMaxY = stream.readSint32LE();
+ randomMoveMinZ = stream.readSint32LE();
+ randomMoveMaxZ = stream.readSint32LE();
+
+ fixedPosX = stream.readSint32LE();
+ fixedPosY = stream.readSint32LE();
+ fixedPosZ = stream.readSint32LE();
+
+ moveStepTime = stream.readUint32LE();
+ numMoveSteps = stream.readSint32LE();
+
+ linearMoveStartX = stream.readSint32LE();
+ linearMoveEndX = stream.readSint32LE();
+ linearMoveStartY = stream.readSint32LE();
+ linearMoveEndY = stream.readSint32LE();
+ linearMoveStartZ = stream.readSint32LE();
+ linearMoveEndX = stream.readSint32LE();
+
+ rotateMoveStartX = stream.readSint32LE();
+ rotateMoveStartY = stream.readSint32LE();
+ rotateMoveStartZ = stream.readSint32LE();
+ rotateMoveAxis = stream.readByte();
+
+ minDistance = stream.readUint32LE();
+ maxDistance = stream.readUint32LE();
+}
+
void SoundDescription::readNormal(Common::SeekableReadStream &stream) {
Common::Serializer s(&stream, nullptr);
s.setVersion(g_nancy->getGameType());
@@ -140,8 +179,6 @@ void SoundDescription::readDIGI(Common::SeekableReadStream &stream) {
s.syncAsUint16LE(panAnchorFrame, kGameTypeVampire, kGameTypeNancy2);
s.skip(2, kGameTypeVampire, kGameTypeNancy2);
-
- s.skip(0x61, kGameTypeNancy3);
}
void SoundDescription::readMenu(Common::SeekableReadStream &stream) {
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 8b0566853f5..56bc5d8a9e4 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -25,6 +25,7 @@
#include "common/rect.h"
#include "common/array.h"
#include "common/str.h"
+#include "math/vector3d.h"
namespace Common {
class SeekableReadStream;
@@ -108,6 +109,9 @@ struct SceneChangeDescription {
int8 paletteID = -1; // TVD only
+ Math::Vector3d listenerFrontVector;
+ uint16 frontVectorFrameID = 0;
+
void readData(Common::SeekableReadStream &stream, bool longFormat = false);
};
@@ -159,6 +163,43 @@ struct SecondaryVideoDescription {
void readData(Common::SeekableReadStream &stream);
};
+// Describes set of effects that can be applied to sounds.
+struct SoundEffectDescription {
+ uint32 minTimeDelay = 0;
+ uint32 maxTimeDelay = 0;
+
+ int32 randomMoveMinX = 0;
+ int32 randomMoveMaxX = 0;
+ int32 randomMoveMinY = 0;
+ int32 randomMoveMaxY = 0;
+ int32 randomMoveMinZ = 0;
+ int32 randomMoveMaxZ = 0;
+
+ int32 fixedPosX = 0;
+ int32 fixedPosY = 0;
+ int32 fixedPosZ = 0;
+
+ uint32 moveStepTime = 0;
+ int32 numMoveSteps = 0;
+
+ int32 linearMoveStartX = 0;
+ int32 linearMoveEndX = 0;
+ int32 linearMoveStartY = 0;
+ int32 linearMoveEndY = 0;
+ int32 linearMoveStartZ = 0;
+ int32 linearMoveEndZ = 0;
+
+ int32 rotateMoveStartX = 0;
+ int32 rotateMoveStartY = 0;
+ int32 rotateMoveStartZ = 0;
+ byte rotateMoveAxis = 0;
+
+ uint32 minDistance = 0;
+ uint32 maxDistance = 0;
+
+ void readData(Common::SeekableReadStream &stream);
+};
+
// Descrbes a single sound. Combines four different structs found in the data in one
struct SoundDescription {
Common::String name;
@@ -168,6 +209,7 @@ struct SoundDescription {
uint16 volume = 0;
uint16 panAnchorFrame = 0;
uint32 samplesPerSec = 0;
+ bool isPanning = false;
void readNormal(Common::SeekableReadStream &stream);
void readDIGI(Common::SeekableReadStream &stream);
diff --git a/engines/nancy/console.cpp b/engines/nancy/console.cpp
index 9d94e08ecdd..52340522603 100644
--- a/engines/nancy/console.cpp
+++ b/engines/nancy/console.cpp
@@ -415,7 +415,9 @@ bool NancyConsole::Cmd_loadScene(int argc, const char **argv) {
return true;
}
- NancySceneState.changeScene((uint16)atoi(argv[1]), 0, 0, false);
+ SceneChangeDescription scene;
+ scene.sceneID = (uint16)atoi(argv[1]);
+ NancySceneState.changeScene(scene);
NancySceneState._state = State::Scene::kLoad;
return cmdExit(0, nullptr);
}
diff --git a/engines/nancy/sound.cpp b/engines/nancy/sound.cpp
index 66d6ea293f9..922bb2ba769 100644
--- a/engines/nancy/sound.cpp
+++ b/engines/nancy/sound.cpp
@@ -21,6 +21,9 @@
#include "common/system.h"
#include "common/substream.h"
+#include "common/random.h"
+
+#include "math/quat.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
@@ -211,9 +214,7 @@ Audio::SeekableAudioStream *SoundManager::makeHISStream(Common::SeekableReadStre
return Audio::makeVorbisStream(subStream, DisposeAfterUse::YES);
}
-SoundManager::SoundManager() {
- _mixer = g_system->getMixer();
-}
+SoundManager::SoundManager() : _shouldRecalculate(false), _mixer(g_system->getMixer()) {}
void SoundManager::loadCommonSounds(IFF *boot) {
// Persistent sounds that are used across the engine. These originally get loaded inside Logo
@@ -250,7 +251,7 @@ SoundManager::~SoundManager() {
stopAllSounds();
}
-void SoundManager::loadSound(const SoundDescription &description, bool panning) {
+void SoundManager::loadSound(const SoundDescription &description, SoundEffectDescription **effectData) {
if (description.name == "NO SOUND") {
return;
}
@@ -269,7 +270,14 @@ void SoundManager::loadSound(const SoundDescription &description, bool panning)
chan.numLoops = description.numLoops;
chan.volume = description.volume;
chan.panAnchorFrame = description.panAnchorFrame;
- chan.isPanning = panning;
+ chan.isPanning = description.isPanning;
+
+ if (effectData) {
+ // Channel takes ownership of the effect data
+ delete chan.effectData;
+ chan.effectData = *effectData;
+ *effectData = nullptr;
+ }
Common::SeekableReadStream *file = SearchMan.createReadStreamForMember(description.name + (g_nancy->getGameType() == kGameTypeVampire ? ".dwd" : ".his"));
if (file) {
@@ -278,7 +286,7 @@ void SoundManager::loadSound(const SoundDescription &description, bool panning)
}
void SoundManager::playSound(uint16 channelID) {
- if (channelID > 31 || _channels[channelID].stream == nullptr)
+ if (channelID >= _channels.size() || _channels[channelID].stream == nullptr)
return;
Channel &chan = _channels[channelID];
@@ -288,16 +296,71 @@ void SoundManager::playSound(uint16 channelID) {
debugC(kDebugSound, "Unhandled playCommand type 0x%08x! Sound name: %s", chan.playCommands, chan.name.c_str());
}
+ // Init 3D sound
+ if (chan.playCommands & ~kPlaySequential && chan.effectData) {
+ uint16 playCommands = chan.playCommands;
+
+ if (playCommands & kPlayRandomPosition) {
+ auto *rand = g_nancy->_randomSource;
+ chan.position.set(
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinX, chan.effectData->randomMoveMaxX),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinY, chan.effectData->randomMoveMaxY),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinZ, chan.effectData->randomMoveMaxZ));
+ }
+
+ if (playCommands == kPlaySequentialPosition) {
+ chan.position.set(chan.effectData->fixedPosX, chan.effectData->fixedPosY, chan.effectData->fixedPosZ);
+ } else if (playCommands == kPlaySequentialFrameAnchor) {
+ // Doesn't seem to be used so we skip implementing it
+ warning("Sound play command kPlaySequentialFrameAnchor not implemented");
+ } else if (playCommands == kPlayMoveLinear) {
+ chan.position.set(chan.effectData->linearMoveStartX, chan.effectData->linearMoveStartY, chan.effectData->linearMoveStartZ);
+ chan.positionDelta.set(chan.effectData->linearMoveEndX, chan.effectData->linearMoveEndY, chan.effectData->linearMoveEndZ);
+ chan.positionDelta -= chan.position;
+ chan.positionDelta /= chan.effectData->numMoveSteps;
+ chan.nextStepTime = g_nancy->getTotalPlayTime() + chan.effectData->moveStepTime;
+ chan.stepsLeft = chan.effectData->numMoveSteps;
+ } else if (playCommands == kPlayRandomMove) {
+ auto *rand = g_nancy->_randomSource;
+ chan.position.set(
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinX, chan.effectData->randomMoveMaxX),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinY, chan.effectData->randomMoveMaxY),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinZ, chan.effectData->randomMoveMaxZ));
+
+ chan.positionDelta.set(
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinX, chan.effectData->randomMoveMaxX),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinY, chan.effectData->randomMoveMaxY),
+ rand->getRandomNumberRngSigned(chan.effectData->randomMoveMinZ, chan.effectData->randomMoveMaxZ));
+
+ chan.positionDelta -= chan.position;
+ chan.positionDelta /= chan.effectData->numMoveSteps;
+ chan.nextStepTime = g_nancy->getTotalPlayTime() + chan.effectData->moveStepTime;
+ chan.stepsLeft = chan.effectData->numMoveSteps;
+ } else if (playCommands == kPlayMoveCircular) {
+ chan.position.set(chan.effectData->rotateMoveStartX, chan.effectData->rotateMoveStartY, chan.effectData->rotateMoveStartZ);
+ chan.nextStepTime = g_nancy->getTotalPlayTime() + chan.effectData->moveStepTime;
+ chan.stepsLeft = chan.effectData->numMoveSteps;
+ }
+ }
+
+ uint numLoops = chan.numLoops;
+ if (chan.playCommands & kPlayRandomTime) {
+ // We want to add randomized time delays between repeats, which is not doable with
+ // a simple LoopingAudioStream. The delays are added in soundEffectMaintenance();
+ numLoops = 1;
+
+ // Decrement the number of loops since we start playing immediately after
+ --chan.numLoops;
+ }
+
_mixer->playStream( chan.type,
&chan.handle,
- Audio::makeLoopingAudioStream(chan.stream, chan.numLoops),
+ Audio::makeLoopingAudioStream(chan.stream, numLoops),
channelID,
chan.volume * 255 / 100,
0, DisposeAfterUse::NO);
- if (chan.isPanning) {
- calculatePan(channelID);
- }
+ soundEffectMaintenance(channelID);
}
void SoundManager::playSound(const SoundDescription &description) {
@@ -317,7 +380,7 @@ void SoundManager::playSound(const Common::String &chunkName) {
}
void SoundManager::pauseSound(uint16 channelID, bool pause) {
- if (channelID > 31)
+ if (channelID >= _channels.size())
return;
if (isSoundPlaying(channelID)) {
@@ -336,10 +399,15 @@ void SoundManager::pauseSound(const Common::String &chunkName, bool pause) {
}
bool SoundManager::isSoundPlaying(uint16 channelID) const {
- if (channelID > 31)
+ if (channelID >= _channels.size() || !_channels[channelID].stream)
return false;
- return _mixer->isSoundHandleActive(_channels[channelID].handle);
+ const Channel &chan = _channels[channelID];
+ if (chan.playCommands & kPlayRandomTime) {
+ return _mixer->isSoundHandleActive(chan.handle) || chan.numLoops != 0;
+ } else {
+ return _mixer->isSoundHandleActive(chan.handle);
+ }
}
bool SoundManager::isSoundPlaying(const SoundDescription &description) const {
@@ -355,7 +423,7 @@ bool SoundManager::isSoundPlaying(const Common::String &chunkName) const {
}
void SoundManager::stopSound(uint16 channelID) {
- if (channelID > 31)
+ if (channelID >= _channels.size())
return;
Channel &chan = _channels[channelID];
@@ -369,6 +437,12 @@ void SoundManager::stopSound(uint16 channelID) {
chan.name = Common::String();
delete chan.stream;
chan.stream = nullptr;
+ delete chan.effectData;
+ chan.effectData = nullptr;
+ chan.position.set(0, 0, 0);
+ chan.positionDelta.set(0, 0, 0);
+ chan.stepsLeft = 0;
+ chan.nextStepTime = 0;
}
}
@@ -388,14 +462,181 @@ void SoundManager::stopAllSounds() {
}
}
-void SoundManager::calculatePan(uint16 channelID) {
- uint16 viewportFrameID = NancySceneState.getSceneInfo().frameID;
- const State::Scene::SceneSummary &sceneSummary = NancySceneState.getSceneSummary();
+void SoundManager::setVolume(uint16 channelID, uint16 volume) {
+ _mixer->setChannelVolume(_channels[channelID].handle, volume);
+}
+
+void SoundManager::setVolume(const SoundDescription &description, uint16 volume) {
+ setVolume(description.channelID, volume);
+}
+
+void SoundManager::setVolume(const Common::String &chunkName, uint16 volume) {
+ setVolume(_commonSounds[chunkName], volume);
+}
+
+void SoundManager::setRate(uint16 channelID, uint32 rate) {
+ _mixer->setChannelRate(_channels[channelID].handle, rate);
+}
+
+void SoundManager::setRate(const SoundDescription &description, uint32 rate) {
+ setRate(description.channelID, rate);
+}
+
+void SoundManager::setRate(const Common::String &chunkName, uint32 rate) {
+ setRate(_commonSounds[chunkName], rate);
+}
+
+void SoundManager::recalculateSoundEffects() {
+ _shouldRecalculate = true;
+
+ if (g_nancy->getGameType() >= kGameTypeNancy3) {
+ const Nancy::State::Scene::SceneSummary &sceneSummary = NancySceneState.getSceneSummary();
+ SceneChangeDescription &sceneInfo = NancySceneState.getSceneInfo();
+ Math::Vector3d rotatedFrontVector = NancySceneState.getSceneInfo().listenerFrontVector;
+ rotatedFrontVector.normalize();
+
+ int rotation = sceneInfo.frontVectorFrameID < sceneInfo.frameID ?
+ 360 - (sceneInfo.frameID - sceneInfo.frontVectorFrameID) * sceneSummary.degreesPerRotation :
+ (sceneInfo.frontVectorFrameID - sceneInfo.frameID) * sceneSummary.degreesPerRotation;
+
+ Math::Quaternion quat = Math::Quaternion::yAxis(rotation);
+ quat.transform(rotatedFrontVector);
+
+ _orientation = rotatedFrontVector;
+
+ for (uint i = 0; i < 3; ++i) {
+ if (abs(_orientation.getValue(i)) < Math::epsilon) {
+ _orientation.setValue(i, 0);
+ }
+ }
+ }
+}
+
+void SoundManager::stopAndUnloadSpecificSounds() {
+ byte numSSChans = g_nancy->getStaticData().soundChannelInfo.numSceneSpecificChannels;
+
+ if (g_nancy->getGameType() == kGameTypeVampire && Nancy::State::Map::hasInstance()) {
+ // Don't stop the map sound in certain scenes
+ uint nextScene = NancySceneState.getNextSceneInfo().sceneID;
+ if (nextScene != 0 && (nextScene < 15 || nextScene > 27)) {
+ stopSound(NancyMapState.getSound());
+ }
+ }
+
+ for (uint i = 0; i < numSSChans; ++i) {
+ stopSound(i);
+ }
+
+ stopSound("MSND");
+}
+
+void SoundManager::initSoundChannels() {
+ const SoundChannelInfo &channelInfo = g_nancy->getStaticData().soundChannelInfo;
+
+ _channels.resize(channelInfo.numChannels);
+
+ for (const short id : channelInfo.speechChannels) {
+ _channels[id].type = Audio::Mixer::SoundType::kSpeechSoundType;
+ }
+
+ for (const short id : channelInfo.musicChannels) {
+ _channels[id].type = Audio::Mixer::SoundType::kMusicSoundType;
+ }
+
+ for (const short id : channelInfo.sfxChannels) {
+ _channels[id].type = Audio::Mixer::SoundType::kSFXSoundType;
+ }
+}
+
+SoundManager::Channel::~Channel() {
+ delete stream;
+ delete effectData;
+}
+
+void SoundManager::soundEffectMaintenance() {
+ for (uint i = 0; i < _channels.size(); ++i) {
+ soundEffectMaintenance(i);
+ }
+
+ _shouldRecalculate = false;
+}
+
+void SoundManager::soundEffectMaintenance(uint16 channelID) {
+ if (channelID >= _channels.size() || !isSoundPlaying(channelID))
+ return;
+
+ uint32 gameTime = g_nancy->getTotalPlayTime();
Channel &chan = _channels[channelID];
- if (chan.isPanning) {
+
+ // Handle sound effects and 3D sound, which started being used from nancy3.
+ // The original engine used DirectSound 3D, whose effects are only approximated.
+ // In particular, there are some slight but noticeable differences in panning
+ bool hasStepped = false;
+ if (g_nancy->getGameType() >= 3 && chan.effectData) {
+ uint16 playCommands = chan.playCommands;
+ SoundEffectDescription *effectData = chan.effectData;
+
+ // Handle randomized time delay between repeats
+ if (playCommands & kPlayRandomTime && !_mixer->isSoundHandleActive(chan.handle) && chan.numLoops != 0) {
+ if (chan.nextRepeatTime == 0) {
+ // Channel just stopped playing, add a randomized delay
+ chan.nextRepeatTime = g_nancy->_randomSource->getRandomNumberRng(effectData->minTimeDelay, effectData->maxTimeDelay) + gameTime;
+ } else if (chan.nextRepeatTime < gameTime) {
+ // Delay is over, start playing again
+ _mixer->playStream( chan.type,
+ &chan.handle,
+ chan.stream,
+ channelID,
+ chan.volume * 255 / 100,
+ 0, DisposeAfterUse::NO);
+
+ --chan.numLoops;
+ chan.nextRepeatTime = 0;
+ }
+ }
+
+ // Move sound in space
+ if (playCommands & kPlayMoveLinear && chan.stepsLeft && gameTime > chan.nextStepTime) {
+ chan.nextStepTime = gameTime + chan.effectData->moveStepTime;
+ --chan.stepsLeft;
+ hasStepped = true;
+
+ if (playCommands == kPlayMoveCircular) {
+ // No real uses at least up to nancy5, so this is untested
+ Math::Quaternion quat;
+ switch (chan.effectData->rotateMoveAxis) {
+ case kRotateAroundX:
+ quat = Math::Quaternion::xAxis(360 / chan.effectData->numMoveSteps);
+ break;
+ case kRotateAroundY:
+ quat = Math::Quaternion::yAxis(360 / chan.effectData->numMoveSteps);
+ break;
+ case kRotateAroundZ:
+ quat = Math::Quaternion::zAxis(360 / chan.effectData->numMoveSteps);
+ break;
+ }
+
+ quat.transform(chan.position);
+ } else {
+ chan.position += chan.positionDelta;
+ }
+ }
+ }
+
+ // Check if the player has moved OR if the sound itself has moved
+ if (!_shouldRecalculate && !hasStepped) {
+ return;
+ }
+
+ uint16 viewportFrameID = NancySceneState.getSceneInfo().frameID;
+
+ // Old panning algorithm, used in The Vampire Diaries
+ if (g_nancy->getGameType() <= kGameTypeNancy2 && chan.isPanning) {
+ const State::Scene::SceneSummary &sceneSummary = NancySceneState.getSceneSummary();
+
switch (sceneSummary.totalViewAngle) {
case 180:
- _mixer->setChannelBalance(chan.handle, CLIP<int32>((viewportFrameID - chan.panAnchorFrame) * sceneSummary.soundPanPerFrame * 364, -32768, 32767) / 256);
+ _mixer->setChannelBalance(chan.handle, CLIP<int32>((viewportFrameID - chan.panAnchorFrame) * sceneSummary.degreesPerRotation * 364, -32768, 32767) / 256);
break;
case 360: {
int16 adjustedViewportFrame = viewportFrameID - chan.panAnchorFrame;
@@ -433,83 +674,70 @@ void SoundManager::calculatePan(uint16 channelID) {
_mixer->setChannelBalance(chan.handle, (balance - 32768) / 256);
break;
- }
+ }
default:
_mixer->setChannelBalance(chan.handle, 0);
break;
}
}
-}
-void SoundManager::calculatePan(const SoundDescription &description) {
- if (description.name != "NO SOUND") {
- calculatePan(description.channelID);
- }
-}
-
-void SoundManager::calculatePanForAllSounds() {
- for (uint i = 0; i < 31; ++i) {
- calculatePan(i);
- }
-}
-
-void SoundManager::setVolume(uint16 channelID, uint16 volume) {
- _mixer->setChannelVolume(_channels[channelID].handle, volume);
-}
-
-void SoundManager::setVolume(const SoundDescription &description, uint16 volume) {
- setVolume(description.channelID, volume);
-}
+ // Panning/volume/rate adjustment used in nancy3 and up. Originally handled by DirectSound 3D
+ if (g_nancy->getGameType() >= 3 && chan.effectData) {
+ const Math::Vector3d &listenerPos = NancySceneState.getSceneSummary().listenerPosition;
+ float dist = listenerPos.getDistanceTo(chan.position);
+ float volume;
-void SoundManager::setVolume(const Common::String &chunkName, uint16 volume) {
- setVolume(_commonSounds[chunkName], volume);
-}
-
-void SoundManager::setRate(uint16 channelID, uint32 rate) {
- _mixer->setChannelRate(_channels[channelID].handle, rate);
-}
+ // Panning is linear, so we calculate it from the difference in degrees
+ Math::Vector3d relativeSoundPos = chan.position - listenerPos;
+ float pan = Math::Vector3d::angle(_orientation, relativeSoundPos.getNormalized()).getDegrees();
+ if (pan > 90) {
+ pan = 90 - (pan - 90);
+ }
-void SoundManager::setRate(const SoundDescription &description, uint32 rate) {
- setRate(description.channelID, rate);
-}
+ pan /= 90;
-void SoundManager::setRate(const Common::String &chunkName, uint32 rate) {
- setRate(_commonSounds[chunkName], rate);
-}
+ if (Math::Vector3d::crossProduct(_orientation, relativeSoundPos).y() < 0) {
+ pan = -pan;
+ }
-void SoundManager::stopAndUnloadSpecificSounds() {
- byte numSSChans = g_nancy->getStaticData().soundChannelInfo.numSceneSpecificChannels;
+ // Attenuate sound based on distance
+ if (dist < chan.effectData->minDistance) {
+ volume = 255;
+ } else if (dist > chan.effectData->maxDistance) {
+ volume = 255.0 / (2 * log2(chan.effectData->maxDistance - chan.effectData->minDistance + 1));
+ } else {
+ float dlog = (2 * log2(dist - chan.effectData->minDistance + 1));
+ volume = 255.0 / dlog;
- if (g_nancy->getGameType() == kGameTypeVampire && Nancy::State::Map::hasInstance()) {
- // Don't stop the map sound in certain scenes
- uint nextScene = NancySceneState.getNextSceneInfo().sceneID;
- if (nextScene != 0 && (nextScene < 15 || nextScene > 27)) {
- stopSound(NancyMapState.getSound());
+ // Sounds that are closer to the listener shouldn't pan as hard
+ // note: slightly inaccurate, compare the ticking sound in nancy3 scene 4015
+ pan -= pan / dlog;
}
- }
- for (uint i = 0; i < numSSChans; ++i) {
- stopSound(i);
- }
+ // Doppler effect is affected by the velocities of the source and listener,
+ // as projected onto the vector between source and listener
+ Math::Vector3d listenerToSource = chan.position - listenerPos;
+ if (listenerToSource.isZero()) {
+ return;
+ }
- stopSound("MSND");
-}
+ float projectedListenerVelocity, projectedSrcVelocity;
-void SoundManager::initSoundChannels() {
- const SoundChannelInfo &channelInfo = g_nancy->getStaticData().soundChannelInfo;
+ float soundSpeed = 343.0;
- _channels.resize(channelInfo.numChannels);
-
- for (const short id : channelInfo.speechChannels) {
- _channels[id].type = Audio::Mixer::SoundType::kSpeechSoundType;
- }
+ // It appears the original engine's devs either didn't know or didn't care
+ // what the velocity parameters do, so they used the doubled orientation vector
+ // as listener velocity, and the doubled position vector of the sound as source velocity.
+ // This results in physically incorrect behavior which we replicate
+ projectedListenerVelocity = Math::Vector3d::dotProduct(_orientation * 2, listenerToSource) / listenerToSource.length();
+ projectedSrcVelocity = -Math::Vector3d::dotProduct(chan.position * 2, listenerToSource) / listenerToSource.length();
- for (const short id : channelInfo.musicChannels) {
- _channels[id].type = Audio::Mixer::SoundType::kMusicSoundType;
- }
+ // Calculate the final rate of the sound with doppler effect applied
+ uint32 rate = chan.stream->getRate() * (1 + projectedListenerVelocity / soundSpeed) / (1 - projectedSrcVelocity / soundSpeed);
- for (const short id : channelInfo.sfxChannels) {
- _channels[id].type = Audio::Mixer::SoundType::kSFXSoundType;
+ _mixer->setChannelVolume(chan.handle, ((byte)volume * chan.volume) / 100);
+ _mixer->setChannelBalance(chan.handle, pan * 127);
+ _mixer->setChannelRate(chan.handle, rate);
}
}
diff --git a/engines/nancy/sound.h b/engines/nancy/sound.h
index 97a2433ae60..79776406bd4 100644
--- a/engines/nancy/sound.h
+++ b/engines/nancy/sound.h
@@ -32,6 +32,7 @@ class SeekableReadStream;
namespace Audio {
class SeekableAudioStream;
+class QueuingAudioStream;
}
namespace Nancy {
@@ -46,16 +47,16 @@ public:
// Older versions had a different, non-bitflag enum, but testing
// indicates those were never actually implemented
enum PlayCommandFlags {
- kPlaySequential = 1 << 0, // Same as kPlaySequentialAndDie
- kPlayPosition = 1 << 1, // Play at fixed position in 3D space
- kPlayFrameAnchor = 1 << 2, // Position in 3D space is tied to a background frame, ignoring 3D coordinates
+ kPlaySequential = 0x0001, // Play normally
+ kPlaySequentialPosition = 0x0003, // Play at fixed position in 3D space
+ kPlaySequentialFrameAnchor = 0x0007, // Position in 3D space is tied to a background frame, ignoring 3D coordinates
- kPlayRandomTime = 1 << 4, // Play at random time intervals
- kPlayRandomPosition = 1 << 5, // Play at random 3D positions
+ kPlayRandomTime = 0x0010, // Play at random time intervals
+ kPlayRandomPosition = 0x0020, // Play at random 3D positions
- kPlayMoveLinear = 1 << 8, // Move sound position in 3D space. The movement is linear unless kPlayMoveCircular is also set
- kPlayMoveCircular = 1 << 9, // Move sound position in a circular direction (see SoundRotationAxis)
- kPlayRandomMove = 1 << 10 // Move along random vector. Does not combine with kPlayMoveCircular
+ kPlayMoveLinear = 0x0100, // Move sound position in 3D space. The movement is linear unless kPlayMoveCircular is also set
+ kPlayMoveCircular = 0x0300, // Move sound position in a circular direction (see SoundRotationAxis)
+ kPlayRandomMove = 0x0500 // Move along random vector. Does not combine with kPlayMoveCircular
};
enum SoundRotationAxis {
@@ -71,7 +72,7 @@ public:
void initSoundChannels();
// Load a sound into a channel without starting it
- void loadSound(const SoundDescription &description, bool panning = false);
+ void loadSound(const SoundDescription &description, SoundEffectDescription **effectData = nullptr);
void playSound(uint16 channelID);
void playSound(const SoundDescription &description);
@@ -90,10 +91,6 @@ public:
void stopSound(const Common::String &chunkName);
void stopAllSounds();
- void calculatePan(uint16 channelID);
- void calculatePan(const SoundDescription &description);
- void calculatePanForAllSounds();
-
void setVolume(uint16 channelID, uint16 volume);
void setVolume(const SoundDescription &description, uint16 volume);
void setVolume(const Common::String &chunkName, uint16 volume);
@@ -102,6 +99,9 @@ public:
void setRate(const SoundDescription &description, uint32 rate);
void setRate(const Common::String &chunkName, uint32 rate);
+ void soundEffectMaintenance();
+ void recalculateSoundEffects();
+
// Used when changing scenes
void stopAndUnloadSpecificSounds();
@@ -109,6 +109,7 @@ public:
protected:
struct Channel {
+ ~Channel();
Common::String name;
Audio::Mixer::SoundType type = Audio::Mixer::SoundType::kPlainSoundType;
uint16 playCommands = 1;
@@ -119,12 +120,25 @@ protected:
Audio::SeekableAudioStream *stream = nullptr;
Audio::SoundHandle handle;
bool isPersistent = false;
+
+ // Sound effect data, not applicable to nancy2 and below
+ SoundEffectDescription *effectData = nullptr;
+ Math::Vector3d position;
+ Math::Vector3d positionDelta;
+ uint32 nextStepTime = 0;
+ uint16 stepsLeft = 0;
+ uint32 nextRepeatTime = 0;
};
+ void soundEffectMaintenance(uint16 channelID);
+
Audio::Mixer *_mixer;
Common::Array<Channel> _channels;
Common::HashMap<Common::String, SoundDescription> _commonSounds;
+
+ bool _shouldRecalculate;
+ Math::Vector3d _orientation;
};
} // End of namespace Nancy
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 19f3dd86a49..bd951c96de9 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -49,6 +49,9 @@ namespace State {
void Scene::SceneSummary::read(Common::SeekableReadStream &stream) {
char *buf = new char[0x32];
+ int32 x = 0;
+ int32 y = 0;
+ int32 z = 0;
stream.seek(0);
Common::Serializer ser(&stream, nullptr);
@@ -71,11 +74,12 @@ void Scene::SceneSummary::read(Common::SeekableReadStream &stream) {
ser.syncAsUint16LE(panningType);
ser.syncAsUint16LE(numberOfVideoFrames, kGameTypeVampire, kGameTypeNancy2);
- ser.syncAsUint16LE(soundPanPerFrame);
+ ser.syncAsUint16LE(degreesPerRotation);
ser.syncAsUint16LE(totalViewAngle, kGameTypeVampire, kGameTypeNancy2);
- ser.syncAsUint32LE(startX, kGameTypeNancy3);
- ser.syncAsUint32LE(startY, kGameTypeNancy3);
- ser.syncAsUint32LE(startZ, kGameTypeNancy3);
+ ser.syncAsUint32LE(x, kGameTypeNancy3);
+ ser.syncAsUint32LE(y, kGameTypeNancy3);
+ ser.syncAsUint32LE(z, kGameTypeNancy3);
+ listenerPosition.set(x, y, z);
ser.syncAsUint16LE(horizontalScrollDelta);
ser.syncAsUint16LE(verticalScrollDelta);
ser.syncAsUint16LE(horizontalEdgeSize);
@@ -139,7 +143,7 @@ void Scene::process() {
// fall through
case kStartSound:
_state = kRun;
- if (_sceneState.continueSceneSound == kLoadSceneSound) {
+ if (_sceneState.currentScene.continueSceneSound == kLoadSceneSound) {
g_nancy->_sound->stopAndUnloadSpecificSounds();
g_nancy->_sound->loadSound(_sceneState.summary.sound);
g_nancy->_sound->playSound(_sceneState.summary.sound);
@@ -189,38 +193,23 @@ bool Scene::onStateExit(const NancyState::NancyState nextState) {
return false;
}
-void Scene::changeScene(uint16 id, uint16 frame, uint16 verticalOffset, byte continueSceneSound, int8 paletteID) {
- if (id == 9999 || _state == kLoad) {
+void Scene::changeScene(const SceneChangeDescription &sceneDescription) {
+ if (sceneDescription.sceneID == 9999 || _state == kLoad) {
return;
}
- _sceneState.nextScene.sceneID = id;
- _sceneState.nextScene.frameID = frame;
- _sceneState.nextScene.verticalOffset = verticalOffset;
- _sceneState.continueSceneSound = continueSceneSound;
-
- if (paletteID != -1) {
- _sceneState.nextScene.paletteID = paletteID;
- }
-
+ _sceneState.nextScene = sceneDescription;
_state = kLoad;
}
-void Scene::changeScene(const SceneChangeDescription &sceneDescription) {
- changeScene(sceneDescription.sceneID,
- sceneDescription.frameID,
- sceneDescription.verticalOffset,
- sceneDescription.continueSceneSound,
- sceneDescription.paletteID);
-}
-
void Scene::pushScene() {
_sceneState.pushedScene = _sceneState.currentScene;
_sceneState.isScenePushed = true;
}
void Scene::popScene() {
- changeScene(_sceneState.pushedScene.sceneID, _sceneState.pushedScene.frameID, _sceneState.pushedScene.verticalOffset, true);
+ _sceneState.pushedScene.continueSceneSound = true;
+ changeScene(_sceneState.pushedScene);
_sceneState.isScenePushed = false;
}
@@ -409,15 +398,21 @@ void Scene::registerGraphics() {
}
void Scene::synchronize(Common::Serializer &ser) {
- if (ser.isSaving()) {
- ser.syncAsUint16LE(_sceneState.currentScene.sceneID);
- ser.syncAsUint16LE(_sceneState.currentScene.frameID);
- ser.syncAsUint16LE(_sceneState.currentScene.verticalOffset);
- } else if (ser.isLoading()) {
- ser.syncAsUint16LE(_sceneState.nextScene.sceneID);
- ser.syncAsUint16LE(_sceneState.nextScene.frameID);
- ser.syncAsUint16LE(_sceneState.nextScene.verticalOffset);
- _sceneState.continueSceneSound = kLoadSceneSound;
+ ser.syncAsUint16LE(_sceneState.currentScene.sceneID);
+ ser.syncAsUint16LE(_sceneState.currentScene.frameID);
+ ser.syncAsUint16LE(_sceneState.currentScene.verticalOffset);
+
+ if (g_nancy->getGameType() >= kGameTypeNancy3) {
+ ser.syncAsUint16LE(_sceneState.currentScene.frontVectorFrameID);
+
+ for (uint i = 0; i < 3; ++i) {
+ ser.syncAsFloatLE(_sceneState.currentScene.listenerFrontVector.getData()[i]);
+ }
+ }
+
+ if (ser.isLoading()) {
+ _sceneState.currentScene.continueSceneSound = kLoadSceneSound;
+ _sceneState.nextScene = _sceneState.currentScene;
g_nancy->_sound->stopAllSounds();
@@ -536,6 +531,8 @@ void Scene::synchronize(Common::Serializer &ser) {
}
}
}
+
+ // Sync sound data
}
void Scene::init() {
@@ -649,10 +646,16 @@ void Scene::load() {
_sceneState.summary.description.c_str(),
_sceneState.nextScene.frameID,
_sceneState.nextScene.verticalOffset,
- _sceneState.continueSceneSound == kContinueSceneSound ? "kContinueSceneSound" : "kLoadSceneSound");
+ _sceneState.currentScene.continueSceneSound == kContinueSceneSound ? "kContinueSceneSound" : "kLoadSceneSound");
+ SceneChangeDescription lastScene = _sceneState.currentScene;
_sceneState.currentScene = _sceneState.nextScene;
+ // Make sure to discard invalid front vectors and reuse the last one
+ if (_sceneState.currentScene.listenerFrontVector.isZero()) {
+ _sceneState.currentScene.listenerFrontVector = lastScene.listenerFrontVector;
+ }
+
// Search for Action Records, maximum for a scene is 30
Common::SeekableReadStream *actionRecordChunk = nullptr;
@@ -665,6 +668,10 @@ void Scene::load() {
delete actionRecordChunk;
}
+ if (_sceneState.currentScene.paletteID == -1) {
+ _sceneState.currentScene.paletteID = 0;
+ }
+
_viewport.loadVideo(_sceneState.summary.videoFile,
_sceneState.currentScene.frameID,
_sceneState.currentScene.verticalOffset,
@@ -697,6 +704,8 @@ void Scene::load() {
_flags.sceneCounts.getOrCreateVal(_sceneState.currentScene.sceneID)++;
+ g_nancy->_sound->recalculateSoundEffects();
+
_state = kStartSound;
}
@@ -743,6 +752,8 @@ void Scene::run() {
_lightning->run();
}
+ g_nancy->_sound->soundEffectMaintenance();
+
if (_state == kLoad) {
g_nancy->_graphicsManager->suppressNextDraw();
}
@@ -797,7 +808,7 @@ void Scene::handleInput() {
if (_sceneState.currentScene.frameID != _viewport.getCurFrame()) {
_sceneState.currentScene.frameID = _viewport.getCurFrame();
- g_nancy->_sound->calculatePanForAllSounds();
+ g_nancy->_sound->recalculateSoundEffects();
}
_actionManager.handleInput(input);
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 8df4f77ce75..0d63cd330a7 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -67,13 +67,6 @@ class Clock;
namespace State {
-struct SceneInfo {
- uint16 sceneID = 0;
- uint16 frameID = 0;
- uint16 verticalOffset = 0;
- uint16 paletteID = 0;
-};
-
// The game state that handles all of the gameplay
class Scene : public State, public Common::Singleton<Scene> {
friend class Nancy::Action::ActionRecord;
@@ -103,7 +96,7 @@ public:
byte panningType;
uint16 numberOfVideoFrames;
- uint16 soundPanPerFrame;
+ uint16 degreesPerRotation;
uint16 totalViewAngle;
uint16 horizontalScrollDelta;
uint16 verticalScrollDelta;
@@ -113,9 +106,7 @@ public:
Time fastMoveTimeDelta;
// Sound start vectors, used in nancy3 and up
- uint32 startX = 0;
- uint32 startY = 0;
- uint32 startZ = 0;
+ Math::Vector3d listenerPosition;
void read(Common::SeekableReadStream &stream);
};
@@ -128,7 +119,6 @@ public:
void onStateEnter(const NancyState::NancyState prevState) override;
bool onStateExit(const NancyState::NancyState nextState) override;
- void changeScene(uint16 id, uint16 frame, uint16 verticalOffset, byte continueSceneSound, int8 paletteID = -1);
void changeScene(const SceneChangeDescription &sceneDescription);
void pushScene();
void popScene();
@@ -184,8 +174,8 @@ public:
Action::ActionManager &getActionManager() { return _actionManager; }
- SceneInfo &getSceneInfo() { return _sceneState.currentScene; }
- SceneInfo &getNextSceneInfo() { return _sceneState.nextScene; }
+ SceneChangeDescription &getSceneInfo() { return _sceneState.currentScene; }
+ SceneChangeDescription &getNextSceneInfo() { return _sceneState.nextScene; }
const SceneSummary &getSceneSummary() const { return _sceneState.summary; }
void setActiveConversation(Action::ConversationSound *activeConversation);
@@ -220,12 +210,10 @@ private:
struct SceneState {
SceneSummary summary;
- SceneInfo currentScene;
- SceneInfo nextScene;
- SceneInfo pushedScene;
- bool isScenePushed;
-
- uint16 continueSceneSound = kLoadSceneSound;
+ SceneChangeDescription currentScene;
+ SceneChangeDescription nextScene;
+ SceneChangeDescription pushedScene;
+ bool isScenePushed = false;
};
struct Timers {
Commit: b6ae5b4cd1303ca2fcc68d5f6bad42d2a606a8a1
https://github.com/scummvm/scummvm/commit/b6ae5b4cd1303ca2fcc68d5f6bad42d2a606a8a1
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-22T12:15:26+03:00
Commit Message:
NANCY: Allow chained orFlag dependencies
Fixed the dependency code so dependencies marked with
an orFlag can be chained (that is, we can have more than
two of them at a time). This makes the chiming clock in
nancy3 actually work.
Changed paths:
engines/nancy/action/actionmanager.cpp
diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index 3934cbf68d0..d09732141a1 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -233,14 +233,36 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
}
// An orFlag marks that its corresponding dependency and the one after it
- // mutually satisfy each other; if one is satisfied, so is the other
- for (uint i = 1; i < dep.children.size(); ++i) {
- if (dep.children[i - 1].orFlag) {
- if (dep.children[i - 1].satisfied)
- dep.children[i].satisfied = true;
- if (dep.children[i].satisfied)
- dep.children[i - 1].satisfied = true;
+ // mutually satisfy each other; if one is satisfied, so is the other. The effect
+ // can be chained indefinitely (for example, the chiming clock in nancy3)
+ for (uint i = 0; i < dep.children.size(); ++i) {
+ if (dep.children[i].orFlag) {
+ // Found an orFlag, start going down the chain of dependencies with orFlags
+ bool foundSatisfied = false;
+ for (uint j = i; j < dep.children.size(); ++j) {
+ if (dep.children[j].satisfied) {
+ // A dependency has been satisfied
+ foundSatisfied = true;
+ break;
+ }
+
+ if (!dep.children[j].orFlag) {
+ // orFlag chain ended, no satisfied deoendencies
+ break;
+ }
+ }
+
+ if (foundSatisfied) {
+ for (; i < dep.children.size(); ++i) {
+ dep.children[i].satisfied = true;
+ if (!dep.children[i].orFlag) {
+ // Last element of orFlag chain
+ break;
+ }
+ }
+ }
}
+
}
// If all children are satisfied, so is the parent
Commit: 93d8dcadcd02a6c25cbb025a0bc8469cf1717c05
https://github.com/scummvm/scummvm/commit/93d8dcadcd02a6c25cbb025a0bc8469cf1717c05
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-22T12:15:26+03:00
Commit Message:
NANCY: Fix ShowInventoryItem in nancy3 and up
Changed paths:
engines/nancy/action/recordtypes.cpp
diff --git a/engines/nancy/action/recordtypes.cpp b/engines/nancy/action/recordtypes.cpp
index dd9612425ea..8747b847d1b 100644
--- a/engines/nancy/action/recordtypes.cpp
+++ b/engines/nancy/action/recordtypes.cpp
@@ -500,14 +500,24 @@ void ShowInventoryItem::init() {
}
void ShowInventoryItem::readData(Common::SeekableReadStream &stream) {
+ GameType gameType = g_nancy->getGameType();
_objectID = stream.readUint16LE();
readFilename(stream, _imageName);
uint16 numFrames = stream.readUint16LE();
+ if (gameType >= kGameTypeNancy3) {
+ stream.skip(2);
+ }
_bitmaps.resize(numFrames);
for (uint i = 0; i < numFrames; ++i) {
- _bitmaps[i].readData(stream);
+ if (gameType <= kGameTypeNancy2) {
+ _bitmaps[i].readData(stream);
+ } else {
+ _bitmaps[i].frameID = i;
+ readRect(stream, _bitmaps[i].src);
+ readRect(stream, _bitmaps[i].dest);
+ }
}
}
More information about the Scummvm-git-logs
mailing list