[Scummvm-git-logs] scummvm master -> 3bb0094d7d2269152c031f8ea462fc262bab5ffb

elasota noreply at scummvm.org
Sat Apr 1 16:02:30 UTC 2023


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

Summary:
b188d295c3 VCRUISE: Refactor a bunch of things to allow for sound cycles
445de7972c VCRUISE: Refactor sound cache so sounds can be unloaded
c3154d5299 VCRUISE: Keep track of loop state, evict ended sounds.
7f549ce5c5 VCRUISE: Hook up volume controls.
3bb0094d7d VCRUISE: Save format rework


Commit: b188d295c3abd2ebd8940b5261fb4827db1ac7e1
    https://github.com/scummvm/scummvm/commit/b188d295c3abd2ebd8940b5261fb4827db1ac7e1
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-01T12:01:48-04:00

Commit Message:
VCRUISE: Refactor a bunch of things to allow for sound cycles

Changed paths:
    engines/vcruise/runtime.cpp
    engines/vcruise/runtime.h


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 80f7681b956..6049434a0ab 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -87,6 +87,70 @@ void Runtime::RenderSection::init(const Common::Rect &paramRect, const Graphics:
 Runtime::OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)) {
 }
 
+Runtime::StackValue::ValueUnion::ValueUnion() {
+}
+
+Runtime::StackValue::ValueUnion::ValueUnion(StackInt_t iVal) : i(iVal) {
+}
+
+Runtime::StackValue::ValueUnion::ValueUnion(const Common::String &strVal) : s(strVal) {
+}
+
+Runtime::StackValue::ValueUnion::ValueUnion(Common::String &&strVal) : s(Common::move(strVal)) {
+}
+
+Runtime::StackValue::ValueUnion::~ValueUnion() {
+}
+
+Runtime::StackValue::StackValue() : type(kNumber), value(0) {
+	new (&value) ValueUnion(0);
+}
+
+Runtime::StackValue::StackValue(const StackValue &other) : type(kNumber), value(0) {
+	(*this) = other;
+}
+
+Runtime::StackValue::StackValue(StackValue &&other) : type(kNumber), value(0) {
+	(*this) = Common::move(other);
+}
+
+Runtime::StackValue::StackValue(StackInt_t i) : type(kNumber), value(i) {
+}
+
+Runtime::StackValue::StackValue(const Common::String &str) : type(kString), value(str) {
+}
+
+Runtime::StackValue::StackValue(Common::String &&str) : type(kString), value(Common::move(str)) {
+}
+
+Runtime::StackValue::~StackValue() {
+	value.~ValueUnion();
+}
+
+Runtime::StackValue &Runtime::StackValue::operator=(const StackValue &other) {
+	value.~ValueUnion();
+
+	if (other.type == StackValue::kNumber)
+		new (&value) ValueUnion(other.value.i);
+
+	if (other.type == StackValue::kString)
+		new (&value) ValueUnion(other.value.s);
+
+	return *this;
+}
+
+Runtime::StackValue &Runtime::StackValue::operator=(StackValue &&other) {
+	value.~ValueUnion();
+
+	if (other.type == StackValue::kNumber)
+		new (&value) ValueUnion(other.value.i);
+
+	if (other.type == StackValue::kString)
+		new (&value) ValueUnion(Common::move(other.value.s));
+
+	return *this;
+}
+
 Runtime::Gyro::Gyro() {
 	reset();
 }
@@ -304,7 +368,7 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
 }
 
 CachedSound::CachedSound()
-	: rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
+	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
 	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), x(0), y(0), z(0) {
 }
 
@@ -1373,24 +1437,90 @@ void Runtime::findWaves() {
 	}
 }
 
-void Runtime::loadWave(uint soundID, const Common::String &soundName, const Common::ArchiveMemberPtr &archiveMemberPtr) {
+Common::SharedPtr<CachedSound> Runtime::loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr) {
+	for (const Common::SharedPtr<CachedSound> &activeSound : _activeSounds) {
+		if (activeSound->name == soundName)
+			return activeSound;
+	}
+
 	Common::SeekableReadStream *stream = archiveMemberPtr->createReadStream();
 	if (!stream) {
 		warning("Couldn't open read stream for sound '%s'", soundName.c_str());
-		return;
+		return nullptr;
 	}
 
 	Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
 	if (!audioStream) {
 		warning("Couldn't open audio stream for sound '%s'", soundName.c_str());
-		return;
+		return nullptr;
 	}
 
 	Common::SharedPtr<CachedSound> cachedSound(new CachedSound());
-	_cachedSounds[soundID] = cachedSound;
 
 	cachedSound->stream.reset(audioStream);
 	cachedSound->name = soundName;
+	cachedSound->id = soundID;
+
+	bool foundExisting = false;
+	for (Common::SharedPtr<CachedSound> &existingSound : _activeSounds) {
+		if (existingSound->id == soundID) {
+			existingSound = cachedSound;
+			foundExisting = true;
+			break;
+		}
+	}
+
+	if (!foundExisting)
+		_activeSounds.push_back(cachedSound);
+
+	return cachedSound;
+}
+
+void Runtime::resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, CachedSound *&outWave) {
+	Common::String sndName = soundName;
+
+	uint soundID = 0;
+	for (uint i = 0; i < 4; i++)
+		soundID = soundID * 10u + (sndName[i] - '0');
+
+	sndName.toLowercase();
+
+	outSoundID = soundID;
+	outWave = nullptr;
+
+	for (const Common::SharedPtr<CachedSound> &snd : _activeSounds) {
+		if (snd->name == sndName) {
+			outWave = snd.get();
+			return;
+		}
+	}
+
+	Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator waveIt = _waves.find(sndName);
+
+	if (waveIt != _waves.end()) {
+		Common::SharedPtr<CachedSound> snd = loadWave(sndName, soundID, waveIt->_value);
+		outWave = snd.get();
+	}
+}
+
+void Runtime::resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, CachedSound *&outWave) {
+	outSoundID = 0;
+	outWave = nullptr;
+
+	if (stackValue.type == StackValue::kNumber) {
+		outSoundID = stackValue.value.i;
+
+		for (const Common::SharedPtr<CachedSound> &snd : _activeSounds) {
+			if (snd->id == static_cast<uint>(stackValue.value.i)) {
+				outWave = snd.get();
+				break;
+			}
+		}
+		return;
+	}
+
+	if (stackValue.type == StackValue::kString)
+		resolveSoundByName(stackValue.value.s, outSoundID, outWave);
 }
 
 void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
@@ -1853,28 +1983,13 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
 	debug(1, "Animation last frame set to %u", animDef.lastFrame);
 }
 
-void Runtime::setSound3DParameters(uint soundID, int32 x, int32 y, const SoundParams3D &soundParams3D) {
-	Common::HashMap<uint, Common::SharedPtr<CachedSound> >::iterator it = _cachedSounds.find(soundID);
-	if (it == _cachedSounds.end()) {
-		warning("Couldn't set sound parameters for sound ID %u, the sound wasn't loaded", soundID);
-		return;
-	}
-
-	CachedSound &snd = *it->_value;
+void Runtime::setSound3DParameters(CachedSound &snd, int32 x, int32 y, const SoundParams3D &soundParams3D) {
 	snd.x = x;
 	snd.y = y;
 	snd.params3D = soundParams3D;
 }
 
-void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balance, bool is3D) {
-	Common::HashMap<uint, Common::SharedPtr<CachedSound> >::iterator it = _cachedSounds.find(soundID);
-	if (it == _cachedSounds.end()) {
-		warning("Couldn't trigger sound ID %u, the sound wasn't loaded", soundID);
-		return;
-	}
-
-	CachedSound &snd = *it->_value;
-
+void Runtime::triggerSound(bool looping, CachedSound &snd, uint volume, int32 balance, bool is3D) {
 	snd.volume = volume;
 	snd.balance = balance;
 	snd.is3D = is3D;
@@ -1911,13 +2026,7 @@ void Runtime::triggerSound(bool looping, uint soundID, uint volume, int32 balanc
 	}
 }
 
-void Runtime::triggerSoundRamp(uint soundID, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
-	Common::HashMap<uint, Common::SharedPtr<CachedSound> >::const_iterator it = _cachedSounds.find(soundID);
-	if (it == _cachedSounds.end())
-		return;
-
-	CachedSound &snd = *it->_value;
-
+void Runtime::triggerSoundRamp(CachedSound &snd, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
 	snd.rampStartVolume = snd.volume;
 	snd.rampEndVolume = newVolume;
 	snd.rampTerminateOnCompletion = terminateOnCompletion;
@@ -1931,8 +2040,8 @@ void Runtime::triggerSoundRamp(uint soundID, uint durationMSec, uint newVolume,
 void Runtime::updateSounds(uint32 timestamp) {
 	Common::Array<uint> condemnedSounds;
 
-	for (const Common::HashMap<uint, Common::SharedPtr<CachedSound> >::Node &node : _cachedSounds) {
-		CachedSound &snd = *node._value;
+	for (uint sndIndex = 0; sndIndex < _activeSounds.size(); sndIndex++) {
+		CachedSound &snd = *_activeSounds[sndIndex];
 
 		if (snd.rampRatePerMSec) {
 			uint ramp = snd.rampRatePerMSec * (timestamp - snd.rampStartTime);
@@ -1941,7 +2050,7 @@ void Runtime::updateSounds(uint32 timestamp) {
 				snd.rampRatePerMSec = 0;
 				newVolume = snd.rampEndVolume;
 				if (snd.rampTerminateOnCompletion)
-					condemnedSounds.push_back(node._key);
+					condemnedSounds.push_back(sndIndex);
 			} else {
 				uint rampedVolume = (snd.rampStartVolume * (65536u - ramp)) + (snd.rampEndVolume * ramp);
 				newVolume = rampedVolume >> 16;
@@ -1958,13 +2067,19 @@ void Runtime::updateSounds(uint32 timestamp) {
 		}
 	}
 
-	for (uint id : condemnedSounds)
-		_cachedSounds.erase(id);
+	// condemnedSounds must be sorted
+	if (condemnedSounds.size()) {
+		uint numDeleted = 0;
+		for (uint index : condemnedSounds) {
+			_activeSounds.remove_at(index - numDeleted);
+			numDeleted++;
+		}
+	}
 }
 
 void Runtime::update3DSounds() {
-	for (const Common::HashMap<uint, Common::SharedPtr<CachedSound> >::Node &node : _cachedSounds) {
-		CachedSound &snd = *node._value;
+	for (const Common::SharedPtr<CachedSound> &sndPtr : _activeSounds) {
+		CachedSound &snd = *sndPtr;
 
 		if (!snd.is3D)
 			continue;
@@ -2044,7 +2159,7 @@ bool Runtime::computeEffectiveVolumeAndBalance(CachedSound &snd) {
 	return changed;
 }
 
-AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
+AnimationDef Runtime::stackArgsToAnimDef(const StackInt_t *args) const {
 	AnimationDef def;
 	def.animNum = args[0];
 	def.firstFrame = args[1];
@@ -2061,14 +2176,14 @@ AnimationDef Runtime::stackArgsToAnimDef(const StackValue_t *args) const {
 }
 
 void Runtime::pushAnimDef(const AnimationDef &animDef) {
-	_scriptStack.push_back(animDef.animNum);
-	_scriptStack.push_back(animDef.firstFrame);
-	_scriptStack.push_back(animDef.lastFrame);
+	_scriptStack.push_back(StackValue(animDef.animNum));
+	_scriptStack.push_back(StackValue(animDef.firstFrame));
+	_scriptStack.push_back(StackValue(animDef.lastFrame));
 
-	_scriptStack.push_back(animDef.constraintRect.left);
-	_scriptStack.push_back(animDef.constraintRect.top);
-	_scriptStack.push_back(animDef.constraintRect.right);
-	_scriptStack.push_back(animDef.constraintRect.bottom);
+	_scriptStack.push_back(StackValue(animDef.constraintRect.left));
+	_scriptStack.push_back(StackValue(animDef.constraintRect.top));
+	_scriptStack.push_back(StackValue(animDef.constraintRect.right));
+	_scriptStack.push_back(StackValue(animDef.constraintRect.bottom));
 
 	uint animNameIndex = 0;
 	Common::HashMap<Common::String, uint>::const_iterator nameIt = _animDefNameToIndex.find(animDef.animName);
@@ -2079,7 +2194,7 @@ void Runtime::pushAnimDef(const AnimationDef &animDef) {
 	} else
 		animNameIndex = nameIt->_value;
 
-	_scriptStack.push_back(animNameIndex);
+	_scriptStack.push_back(StackValue(animNameIndex));
 }
 
 void Runtime::activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars) {
@@ -2610,38 +2725,78 @@ Runtime::LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
 #endif
 
 #define PEEK_STACK(n)                                                                         \
-	if (this->_scriptStack.size() < (n)) {                                                      \
+	if (this->_scriptStack.size() < (n)) {                                                    \
 		error("Script stack underflow");                                                      \
 		return;                                                                               \
 	}                                                                                         \
-	const ScriptArg_t *stackArgs = &this->_scriptStack[this->_scriptStack.size() - (n)]
+	const StackValue *stackArgs = &this->_scriptStack[this->_scriptStack.size() - (n)]
 
 
-#define TAKE_STACK(n)                                                                         \
-	StackValue_t stackArgs[n];                                                                \
+#define TAKE_STACK_INT_NAMED(n, arrayName)                                                    \
+	StackInt_t arrayName[n];                                                                  \
 	do {                                                                                      \
 		const uint stackSize = _scriptStack.size();                                           \
 		if (stackSize < (n)) {                                                                \
 			error("Script stack underflow");                                                  \
 			return;                                                                           \
 		}                                                                                     \
-		const StackValue_t *stackArgsPtr = &this->_scriptStack[stackSize - (n)];              \
-		for (uint i = 0; i < (n); i++)                                                        \
-			stackArgs[i] = stackArgsPtr[i];                                                   \
+		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)];                \
+		for (uint i = 0; i < (n); i++) {                                                      \
+			if (stackArgsPtr[i].type != StackValue::kNumber)                                  \
+				error("Expected op argument %u to be a number");                              \
+			arrayName[i] = stackArgsPtr[i].value.i;                                           \
+		}                                                                                     \
 		this->_scriptStack.resize(stackSize - (n));                                           \
 	} while (false)
 
+#define TAKE_STACK_INT(n) TAKE_STACK_INT_NAMED(n, stackArgs)
+
+#define TAKE_STACK_STR_NAMED(n, arrayName)                                     \
+	Common::String arrayName[n];                                               \
+	do {                                                                       \
+		const uint stackSize = _scriptStack.size();                            \
+		if (stackSize < (n)) {                                                 \
+			error("Script stack underflow");                                   \
+			return;                                                            \
+		}                                                                      \
+		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
+		for (uint i = 0; i < (n); i++) {                                       \
+			if (stackArgsPtr[i].type != StackValue::kNumber)                   \
+				error("Expected op argument %u to be a string");               \
+			arrayName[i] = Common::move(stackArgsPtr[i].value.s);              \
+		}                                                                      \
+		this->_scriptStack.resize(stackSize - (n));                            \
+	} while (false)
+
+#define TAKE_STACK_STR(n) TAKE_STACK_STR_NAMED(n, stackArgs)
+
+#define TAKE_STACK_VAR_NAMED(n, arrayName)                                     \
+	StackValue arrayName[n];                                                   \
+	do {                                                                       \
+		const uint stackSize = _scriptStack.size();                            \
+		if (stackSize < (n)) {                                                 \
+			error("Script stack underflow");                                   \
+			return;                                                            \
+		}                                                                      \
+		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
+		for (uint i = 0; i < (n); i++)                                         \
+			arrayName[i] = Common::move(stackArgsPtr[i]);                      \
+		this->_scriptStack.resize(stackSize - (n));                            \
+	} while (false)
+
+#define TAKE_STACK_VAR(n) TAKE_STACK_VAR_NAMED(n, stackArgs)
+
 #define OPCODE_STUB(op)                           \
 	void Runtime::scriptOp##op(ScriptArg_t arg) { \
 		error("Unimplemented opcode '" #op "'");  \
 	}
 
 void Runtime::scriptOpNumber(ScriptArg_t arg) {
-	_scriptStack.push_back(arg);
+	_scriptStack.push_back(StackValue(arg));
 }
 
 void Runtime::scriptOpRotate(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs + kAnimDefStackArgs);
+	TAKE_STACK_INT(kAnimDefStackArgs + kAnimDefStackArgs);
 
 	_panLeftAnimationDef = stackArgsToAnimDef(stackArgs + 0);
 	_panRightAnimationDef = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
@@ -2649,28 +2804,28 @@ void Runtime::scriptOpRotate(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAngle(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
-	_scriptStack.push_back((stackArgs[0] == static_cast<StackValue_t>(_direction)) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] == static_cast<StackInt_t>(_direction)) ? 1 : 0));
 }
 
 void Runtime::scriptOpAngleGGet(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
-	if (stackArgs[0] < 0 || stackArgs[0] >= static_cast<StackValue_t>(GyroState::kNumGyros))
+	if (stackArgs[0] < 0 || stackArgs[0] >= static_cast<StackInt_t>(GyroState::kNumGyros))
 		error("Invalid gyro index in angleGGet op");
 
-	_scriptStack.push_back(_gyros.gyros[stackArgs[0]].currentState);
+	_scriptStack.push_back(StackValue(_gyros.gyros[stackArgs[0]].currentState));
 }
 
 void Runtime::scriptOpSpeed(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	_scriptEnv.fpsOverride = stackArgs[0];
 }
 
 void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs + 2);
+	TAKE_STACK_INT(kAnimDefStackArgs + 2);
 
 	if (stackArgs[kAnimDefStackArgs] != 0)
 		warning("sanimL second operand wasn't zero (what does that do?)");
@@ -2691,7 +2846,7 @@ void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpChangeL(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	// ChangeL changes the screen number, but it also forces screen entry scripts to replay, which is
 	// needed for things like the fountain.
@@ -2752,7 +2907,7 @@ void Runtime::scriptOpAnimR(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAnimF(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs + 3);
+	TAKE_STACK_INT(kAnimDefStackArgs + 3);
 
 	AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
 
@@ -2782,7 +2937,7 @@ void Runtime::scriptOpAnimF(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAnimN(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	const AnimationDef *faceDirectionAnimDef = nullptr;
 	uint initialFrame = 0;
@@ -2800,7 +2955,7 @@ void Runtime::scriptOpAnimN(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAnimG(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs * 2 + 1);
+	TAKE_STACK_INT(kAnimDefStackArgs * 2 + 1);
 
 	_gyros.posAnim = stackArgsToAnimDef(stackArgs + 0);
 	_gyros.negAnim = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
@@ -2819,7 +2974,7 @@ void Runtime::scriptOpAnimG(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAnimS(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs + 2);
+	TAKE_STACK_INT(kAnimDefStackArgs + 2);
 
 	AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
 
@@ -2835,7 +2990,7 @@ void Runtime::scriptOpAnimS(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAnim(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs + 2);
+	TAKE_STACK_INT(kAnimDefStackArgs + 2);
 
 	AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
 	changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
@@ -2849,7 +3004,7 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpStatic(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs);
+	TAKE_STACK_INT(kAnimDefStackArgs);
 
 	// QUIRK/BUG WORKAROUND: Static animations don't override other static animations!
 	//
@@ -2878,19 +3033,19 @@ void Runtime::scriptOpStatic(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpVarLoad(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[0]);
 
 	Common::HashMap<uint32, int32>::const_iterator it = _variables.find(varID);
 	if (it == _variables.end())
-		_scriptStack.push_back(0);
+		_scriptStack.push_back(StackValue(0));
 	else
-		_scriptStack.push_back(it->_value);
+		_scriptStack.push_back(StackValue(it->_value));
 }
 
 void Runtime::scriptOpVarStore(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[1]);
 
@@ -2898,7 +3053,7 @@ void Runtime::scriptOpVarStore(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpVarAddAndStore(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[0]);
 
@@ -2910,19 +3065,19 @@ void Runtime::scriptOpVarAddAndStore(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpVarGlobalLoad(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	uint32 varID = static_cast<uint32>(stackArgs[0]);
 
 	Common::HashMap<uint32, int32>::const_iterator it = _variables.find(varID);
 	if (it == _variables.end())
-		_scriptStack.push_back(0);
+		_scriptStack.push_back(StackValue(0));
 	else
-		_scriptStack.push_back(it->_value);
+		_scriptStack.push_back(StackValue(it->_value));
 }
 
 void Runtime::scriptOpVarGlobalStore(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	uint32 varID = static_cast<uint32>(stackArgs[1]);
 
@@ -2930,27 +3085,27 @@ void Runtime::scriptOpVarGlobalStore(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpItemCheck(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	for (const InventoryItem &item : _inventory) {
 		if (item.itemID == static_cast<uint>(stackArgs[0])) {
 			_scriptEnv.lastHighlightedItem = item.itemID;
-			_scriptStack.push_back(1);
+			_scriptStack.push_back(StackValue(1));
 			return;
 		}
 	}
 
-	_scriptStack.push_back(0);
+	_scriptStack.push_back(StackValue(0));
 }
 
 void Runtime::scriptOpItemRemove(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	inventoryRemoveItem(stackArgs[0]);
 }
 
 void Runtime::scriptOpItemHighlightSet(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	bool isHighlighted = (stackArgs[1] != 0);
 
@@ -2965,7 +3120,7 @@ void Runtime::scriptOpItemHighlightSet(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpItemAdd(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	if (stackArgs[0] == 0) {
 		// Weird special case, happens in Reah when breaking the glass barrier, this is called with 0 as the parameter.
@@ -2992,23 +3147,23 @@ void Runtime::scriptOpItemClear(ScriptArg_t arg) {
 void Runtime::scriptOpItemHaveSpace(ScriptArg_t arg) {
 	for (const InventoryItem &item : _inventory) {
 		if (item.itemID == 0) {
-			_scriptStack.push_back(1);
+			_scriptStack.push_back(StackValue(1));
 			return;
 		}
 	}
 
-	_scriptStack.push_back(0);
+	_scriptStack.push_back(StackValue(0));
 }
 
 void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= _cursors.size())
 		error("Invalid cursor ID");
 
 	uint resolvedCursorID = stackArgs[0];
 
-	Common::HashMap<StackValue_t, uint>::const_iterator overrideIt = _scriptCursorIDToResourceIDOverride.find(resolvedCursorID);
+	Common::HashMap<StackInt_t, uint>::const_iterator overrideIt = _scriptCursorIDToResourceIDOverride.find(resolvedCursorID);
 	if (overrideIt != _scriptCursorIDToResourceIDOverride.end())
 		resolvedCursorID = overrideIt->_value;
 
@@ -3016,7 +3171,7 @@ void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpSetRoom(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	_roomNumber = stackArgs[0];
 }
@@ -3036,60 +3191,115 @@ void Runtime::scriptOpLMB1(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpSoundS1(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	triggerSound(false, stackArgs[0], 100, 0, false);
+	if (cachedSound)
+		triggerSound(false, *cachedSound, 100, 0, false);
 }
 
 void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT_NAMED(1, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
-	triggerSound(false, stackArgs[0], stackArgs[1], 0, false);
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
+
+	if (cachedSound)
+		triggerSound(false, *cachedSound, sndParamArgs[0], 0, false);
 }
 
 void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	triggerSound(false, stackArgs[0], stackArgs[1], stackArgs[2], false);
+	if (cachedSound)
+		triggerSound(false, *cachedSound, sndParamArgs[0], sndParamArgs[1], false);
 }
 
 void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
-	triggerSound(true, stackArgs[0], 100, 0, false);
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
+
+	if (cachedSound)
+		triggerSound(true, *cachedSound, 100, 0, false);
 }
 
 void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT_NAMED(1, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	triggerSound(true, stackArgs[0], stackArgs[1], 0, false);
+	if (cachedSound)
+		triggerSound(true, *cachedSound, sndParamArgs[0], 0, false);
 }
 
 void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
-	triggerSound(true, stackArgs[0], stackArgs[1], stackArgs[2], false);
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
+
+	if (cachedSound)
+		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], false);
 }
 
 void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT_NAMED(3, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	setSound3DParameters(stackArgs[0], stackArgs[2], stackArgs[3], _pendingSoundParams3D);
-	triggerSound(true, stackArgs[0], stackArgs[1], 0, true);
+	if (cachedSound) {
+		setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
+		triggerSound(true, *cachedSound, sndParamArgs[0], 0, true);
+	}
 }
 
 void Runtime::scriptOp3DSoundL3(ScriptArg_t arg) {
-	TAKE_STACK(5);
+	TAKE_STACK_INT_NAMED(4, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	setSound3DParameters(stackArgs[0], stackArgs[3], stackArgs[4], _pendingSoundParams3D);
-	triggerSound(true, stackArgs[0], stackArgs[1], stackArgs[2], true);
+	if (cachedSound) {
+		setSound3DParameters(*cachedSound, sndParamArgs[2], sndParamArgs[3], _pendingSoundParams3D);
+		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], true);
+	}
 }
 
 void Runtime::scriptOp3DSoundS2(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT_NAMED(3, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	setSound3DParameters(stackArgs[0], stackArgs[2], stackArgs[3], _pendingSoundParams3D);
-	triggerSound(false, stackArgs[0], stackArgs[1], 0, true);
+	if (cachedSound) {
+		setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
+		triggerSound(false, *cachedSound, sndParamArgs[0], 0, true);
+	}
 }
 
 void Runtime::scriptOpStopAL(ScriptArg_t arg) {
@@ -3097,7 +3307,7 @@ void Runtime::scriptOpStopAL(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpAddXSound(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT(4);
 
 	warning("AddXSound not implemented yet");
 	(void)stackArgs;
@@ -3112,14 +3322,14 @@ void Runtime::scriptOpStopSndLA(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpStopSndLO(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	warning("StopSndLO not implemented yet");
 	(void)stackArgs;
 }
 
 void Runtime::scriptOpRange(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 
 	_pendingSoundParams3D.minRange = stackArgs[0];
 	_pendingSoundParams3D.maxRange = stackArgs[1];
@@ -3127,27 +3337,27 @@ void Runtime::scriptOpRange(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpMusic(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	changeMusicTrack(stackArgs[0]);
 }
 
 void Runtime::scriptOpMusicUp(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	warning("Music volume ramp up is not implemented");
 	(void)stackArgs;
 }
 
 void Runtime::scriptOpMusicDn(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	warning("Music volume ramp down is not implemented");
 	(void)stackArgs;
 }
 
 void Runtime::scriptOpParm0(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT(4);
 
 	if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
 		error("Invalid gyro index for Parm0");
@@ -3161,7 +3371,7 @@ void Runtime::scriptOpParm0(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpParm1(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 
 	if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
 		error("Invalid gyro index for Parm1");
@@ -3176,7 +3386,7 @@ void Runtime::scriptOpParm1(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpParm2(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 
 	_gyros.completeInteraction = stackArgs[0];
 	_gyros.failureInteraction = stackArgs[1];
@@ -3187,7 +3397,7 @@ void Runtime::scriptOpParm2(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpParm3(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
 		error("Invalid gyro index for Parm3");
@@ -3199,7 +3409,7 @@ void Runtime::scriptOpParm3(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpParmG(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 
 	int32 gyroSlot = stackArgs[0];
 	int32 dragMargin = stackArgs[1];
@@ -3214,7 +3424,7 @@ void Runtime::scriptOpParmG(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpSParmX(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 
 	_pendingStaticAnimParams.initialDelay = stackArgs[0];
 	_pendingStaticAnimParams.repeatDelay = stackArgs[1];
@@ -3225,7 +3435,7 @@ void Runtime::scriptOpSParmX(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpSAnimX(ScriptArg_t arg) {
-	TAKE_STACK(kAnimDefStackArgs * 2 + 1);
+	TAKE_STACK_INT(kAnimDefStackArgs * 2 + 1);
 
 	AnimationDef animDef1 = stackArgsToAnimDef(stackArgs + 0);
 	AnimationDef animDef2 = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
@@ -3246,113 +3456,158 @@ void Runtime::scriptOpSAnimX(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpVolumeUp3(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
-	triggerSoundRamp(stackArgs[0], stackArgs[1] * 100, stackArgs[2], false);
+	if (cachedSound)
+		triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], false);
 }
 
 void Runtime::scriptOpVolumeDn2(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT_NAMED(1, sndParamArgs);
+	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
 	// FIXME: Just do this instantly
-	triggerSoundRamp(stackArgs[0], 1, stackArgs[1], false);
+	if (cachedSound)
+		triggerSoundRamp(*cachedSound, 1, sndParamArgs[0], false);
 }
 
 void Runtime::scriptOpVolumeDn3(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
-	triggerSoundRamp(stackArgs[0], stackArgs[1] * 100, stackArgs[2], false);
+	if (cachedSound)
+		triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], false);
 }
 
 void Runtime::scriptOpVolumeDn4(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT_NAMED(3, sndParamArgs);
+	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
 
-	triggerSoundRamp(stackArgs[0], stackArgs[1] * 100, stackArgs[2], stackArgs[3] != 0);
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
+
+	if (cachedSound)
+		triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], sndParamArgs[2] != 0);
 }
 
 void Runtime::scriptOpRandom(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	if (stackArgs[0] == 0)
-		_scriptStack.push_back(0);
+		_scriptStack.push_back(StackValue(0));
 	else
-		_scriptStack.push_back(_rng->getRandomNumber(stackArgs[0] - 1));
+		_scriptStack.push_back(StackValue(_rng->getRandomNumber(stackArgs[0] - 1)));
 }
 
 void Runtime::scriptOpDrop(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_VAR(1);
 	(void)stackArgs;
 }
 
 void Runtime::scriptOpDup(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_VAR(1);
 
 	_scriptStack.push_back(stackArgs[0]);
 	_scriptStack.push_back(stackArgs[0]);
 }
 
 void Runtime::scriptOpSwap(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_VAR(2);
 
-	_scriptStack.push_back(stackArgs[1]);
-	_scriptStack.push_back(stackArgs[0]);
+	_scriptStack.push_back(Common::move(stackArgs[1]));
+	_scriptStack.push_back(Common::move(stackArgs[0]));
 }
 
 void Runtime::scriptOpSay1(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	warning("Say1 cycles are not implemented yet, playing first sound in the cycle");
 
-	uint soundID = stackArgs[0];
-	// uint cycleLength = stackArgs[2];
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	triggerSound(false, soundID, 100, 0, false);
+	// uint ? = sndParamArgs[0];
+	// uint cycleLength = sndParamArgs[1];
+
+	if (cachedSound)
+		triggerSound(false, *cachedSound, 100, 0, false);
 }
 
 void Runtime::scriptOpSay3(ScriptArg_t arg) {
-	TAKE_STACK(3);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
-	TriggeredOneShot oneShot;
-	oneShot.soundID = stackArgs[0];
-	oneShot.uniqueSlot = stackArgs[1];
+	if (cachedSound) {
+		TriggeredOneShot oneShot;
+		oneShot.soundID = soundID;
+		oneShot.uniqueSlot = sndParamArgs[0];
 
-	// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
-	if (stackArgs[2] != 1)
-		error("Invalid interrupt arg for say3, only 1 is supported.");
+		// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
+		if (sndParamArgs[1] != 1)
+			error("Invalid interrupt arg for say3, only 1 is supported.");
 
-	if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
-		triggerSound(false, oneShot.soundID, 100, 0, false);
-		_triggeredOneShots.push_back(oneShot);
+		if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
+			triggerSound(false, *cachedSound, 100, 0, false);
+			_triggeredOneShots.push_back(oneShot);
+		}
 	}
 }
 
 void Runtime::scriptOpSay3Get(ScriptArg_t arg) {
-	TAKE_STACK(3);
-
-	TriggeredOneShot oneShot;
-	oneShot.soundID = stackArgs[0];
-	oneShot.uniqueSlot = stackArgs[1];
-
-	// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
-	if (stackArgs[2] != 1)
-		error("Invalid interrupt arg for say3, only 1 is supported.");
-
-	if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
-		triggerSound(false, oneShot.soundID, 100, 0, false);
-		_triggeredOneShots.push_back(oneShot);
-		_scriptStack.push_back(oneShot.soundID);
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	CachedSound *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
+
+	if (cachedSound) {
+		TriggeredOneShot oneShot;
+		oneShot.soundID = soundID;
+		oneShot.uniqueSlot = sndParamArgs[0];
+
+		// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
+		if (sndParamArgs[1] != 1)
+			error("Invalid interrupt arg for say3, only 1 is supported.");
+
+		if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
+			triggerSound(false, *cachedSound, 100, 0, false);
+			_triggeredOneShots.push_back(oneShot);
+			_scriptStack.push_back(StackValue(soundID));
+		} else
+			_scriptStack.push_back(StackValue(0));
 	} else
-		_scriptStack.push_back(0);
+		_scriptStack.push_back(StackValue(0));
 }
 
 void Runtime::scriptOpSetTimer(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	_timers[static_cast<uint>(stackArgs[0])] = g_system->getMillis() + static_cast<uint32>(stackArgs[1]) * 1000u;
 }
 
 void Runtime::scriptOpGetTimer(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	bool isCompleted = true;
 
@@ -3360,11 +3615,11 @@ void Runtime::scriptOpGetTimer(ScriptArg_t arg) {
 	if (timerIt != _timers.end())
 		isCompleted = (g_system->getMillis() >= timerIt->_value);
 
-	_scriptStack.push_back(isCompleted ? 1 : 0);
+	_scriptStack.push_back(StackValue(isCompleted ? 1 : 0));
 }
 
 void Runtime::scriptOpDelay(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	_gameState = kGameStateDelay;
 	_delayCompletionTime = g_system->getMillis() + stackArgs[0];
@@ -3387,7 +3642,7 @@ void Runtime::scriptOpHiGet(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpVerticalPanSet(bool *flags) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	uint baseDirection = static_cast<uint>(stackArgs[0]) % kNumDirections;
 	uint radius = stackArgs[1];
@@ -3411,7 +3666,7 @@ void Runtime::scriptOpVerticalPanSet(bool *flags) {
 }
 
 void Runtime::scriptOpVerticalPanGet() {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	// In any scenario where this is used, there is a corresponding hi/lo set and this only ever triggers off of interactions,
 	// so don't really even need to check anything other than the facing direction?
@@ -3423,11 +3678,11 @@ void Runtime::scriptOpVerticalPanGet() {
 
 	bool isInRadius = (rtDirection <= radius || lfDirection <= radius);
 
-	_scriptStack.push_back(isInRadius ? 1 : 0);
+	_scriptStack.push_back(StackValue(isInRadius ? 1 : 0));
 }
 
 void Runtime::scriptOpSaveAs(ScriptArg_t arg) {
-	TAKE_STACK(4);
+	TAKE_STACK_INT(4);
 
 	// Just ignore this op, it looks like it's for save room remapping of some sort but we allow
 	// saves at any idle screen.
@@ -3443,103 +3698,103 @@ void Runtime::scriptOpExit(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpNot(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
-	_scriptStack.push_back((stackArgs[0] == 0) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] == 0) ? 1 : 0));
 }
 
 void Runtime::scriptOpAnd(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back((stackArgs[0] != 0 && stackArgs[1] != 0) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] != 0 && stackArgs[1] != 0) ? 1 : 0));
 }
 
 void Runtime::scriptOpOr(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back((stackArgs[0] != 0 || stackArgs[1] != 0) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] != 0 || stackArgs[1] != 0) ? 1 : 0));
 }
 
 void Runtime::scriptOpAdd(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back(stackArgs[0] + stackArgs[1]);
+	_scriptStack.push_back(StackValue(stackArgs[0] + stackArgs[1]));
 }
 
 void Runtime::scriptOpSub(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back(stackArgs[0] - stackArgs[1]);
+	_scriptStack.push_back(StackValue(stackArgs[0] - stackArgs[1]));
 }
 
 void Runtime::scriptOpNegate(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
-	_scriptStack.push_back(-stackArgs[0]);
+	_scriptStack.push_back(StackValue(-stackArgs[0]));
 }
 
 void Runtime::scriptOpCmpEq(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back((stackArgs[0] == stackArgs[1]) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] == stackArgs[1]) ? 1 : 0));
 }
 
 void Runtime::scriptOpCmpLt(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back((stackArgs[0] < stackArgs[1]) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] < stackArgs[1]) ? 1 : 0));
 }
 
 void Runtime::scriptOpCmpGt(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
-	_scriptStack.push_back((stackArgs[0] > stackArgs[1]) ? 1 : 0);
+	_scriptStack.push_back(StackValue((stackArgs[0] > stackArgs[1]) ? 1 : 0));
 }
 
 void Runtime::scriptOpBitLoad(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 
-	_scriptStack.push_back((stackArgs[0] >> stackArgs[1]) & 1);
+	_scriptStack.push_back(StackValue((stackArgs[0] >> stackArgs[1]) & 1));
 }
 
 void Runtime::scriptOpBitSet0(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	ScriptArg_t bitMask = static_cast<ScriptArg_t>(1) << stackArgs[1];
-	_scriptStack.push_back(stackArgs[0] & ~bitMask);
+	_scriptStack.push_back(StackValue(stackArgs[0] & ~bitMask));
 }
 
 void Runtime::scriptOpBitSet1(ScriptArg_t arg) {
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 
 	ScriptArg_t bitMask = static_cast<ScriptArg_t>(1) << stackArgs[1];
-	_scriptStack.push_back(stackArgs[0] | bitMask);
+	_scriptStack.push_back(StackValue(stackArgs[0] | bitMask));
 }
 
 void Runtime::scriptOpDisc1(ScriptArg_t arg) {
 	// Disc check, always pass
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 	(void)stackArgs;
-	_scriptStack.push_back(1);
+	_scriptStack.push_back(StackValue(1));
 }
 
 void Runtime::scriptOpDisc2(ScriptArg_t arg) {
 	// Disc check, always pass
-	TAKE_STACK(2);
+	TAKE_STACK_INT(2);
 	(void)stackArgs;
-	_scriptStack.push_back(1);
+	_scriptStack.push_back(StackValue(1));
 }
 
 void Runtime::scriptOpDisc3(ScriptArg_t arg) {
 	// Disc check, always pass
-	TAKE_STACK(3);
+	TAKE_STACK_INT(3);
 	(void)stackArgs;
-	_scriptStack.push_back(1);
+	_scriptStack.push_back(StackValue(1));
 }
 
 void Runtime::scriptOpGoto(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	uint newInteraction = static_cast<uint>(stackArgs[0]);
 
@@ -3569,7 +3824,7 @@ void Runtime::scriptOpGoto(ScriptArg_t arg) {
 }
 
 void Runtime::scriptOpEscOn(ScriptArg_t arg) {
-	TAKE_STACK(1);
+	TAKE_STACK_INT(1);
 
 	_escOn = (stackArgs[0] != 0);
 }
@@ -3611,7 +3866,7 @@ void Runtime::scriptOpValueName(ScriptArg_t arg) {
 	if (it == roomDef->values.end())
 		error("Value '%s' doesn't exist in room %i", varName.c_str(), static_cast<int>(_roomNumber));
 
-	_scriptStack.push_back(it->_value);
+	_scriptStack.push_back(StackValue(it->_value));
 }
 
 void Runtime::scriptOpVarName(ScriptArg_t arg) {
@@ -3628,44 +3883,23 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
 	if (it == roomDef->vars.end())
 		error("Var '%s' doesn't exist in room %i", varName.c_str(), static_cast<int>(_roomNumber));
 
-	_scriptStack.push_back(it->_value);
+	_scriptStack.push_back(StackValue(it->_value));
 }
 
 void Runtime::scriptOpSoundName(ScriptArg_t arg) {
-	Common::String sndName = _scriptSet->strings[arg];
-
-	uint soundID = 0;
-	for (uint i = 0; i < 4; i++)
-		soundID = soundID * 10u + (sndName[i] - '0');
-
-	sndName.toLowercase();
-
-	Common::HashMap<uint, Common::SharedPtr<CachedSound> >::const_iterator cachedSoundIt = _cachedSounds.find(soundID);
-	if (cachedSoundIt != _cachedSounds.end() && cachedSoundIt->_value->name != sndName) {
-		_cachedSounds.erase(cachedSoundIt);
-		cachedSoundIt = _cachedSounds.end();
-	}
-
-	if (cachedSoundIt == _cachedSounds.end()) {
-		Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator waveIt = _waves.find(sndName);
-
-		if (waveIt != _waves.end())
-			loadWave(soundID, sndName, waveIt->_value);
-	}
-
-	_scriptStack.push_back(soundID);
+	_scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
 }
 
 void Runtime::scriptOpCursorName(ScriptArg_t arg) {
 	const Common::String &cursorName = _scriptSet->strings[arg];
 
-	Common::HashMap<Common::String, StackValue_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
+	Common::HashMap<Common::String, StackInt_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
 	if (namedCursorIt == _namedCursors.end()) {
 		error("Unimplemented cursor name '%s'", cursorName.c_str());
 		return;
 	}
 
-	_scriptStack.push_back(namedCursorIt->_value);
+	_scriptStack.push_back(StackValue(namedCursorIt->_value));
 }
 
 void Runtime::scriptOpDubbing(ScriptArg_t arg) {
@@ -3675,7 +3909,7 @@ void Runtime::scriptOpDubbing(ScriptArg_t arg) {
 void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
 	PEEK_STACK(1);
 
-	if (arg == stackArgs[0])
+	if (stackArgs[0].type == StackValue::kNumber && stackArgs[0].value.i == arg)
 		_scriptStack.pop_back();
 	else
 		_scriptNextInstruction++;
@@ -3685,7 +3919,12 @@ void Runtime::scriptOpJump(ScriptArg_t arg) {
 	_scriptNextInstruction = arg;
 }
 
-#undef TAKE_STACK
+#undef TAKE_STACK_STR
+#undef TAKE_STACK_STR_NAMED
+#undef TAKE_STACK_INT
+#undef TAKE_STACK_INT_NAMED
+#undef TAKE_STACK_VAR
+#undef TAKE_STACK_VAR_NAMED
 #undef PEEK_STACK
 #undef OPCODE_STUB
 
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 185030d1b1f..452fc691e86 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -195,6 +195,8 @@ struct CachedSound {
 	Common::SharedPtr<Audio::AudioStream> loopingStream;
 	Common::SharedPtr<AudioPlayer> player;
 
+	uint id;
+
 	uint rampStartVolume;
 	uint rampEndVolume;
 	uint32 rampRatePerMSec;
@@ -450,7 +452,39 @@ private:
 	};
 
 	typedef int32 ScriptArg_t;
-	typedef int32 StackValue_t;
+	typedef int32 StackInt_t;
+
+	struct StackValue {
+		enum StackValueType {
+			kNumber,
+			kString,
+		};
+
+		union ValueUnion {
+			StackInt_t i;
+			Common::String s;
+
+			ValueUnion();
+			explicit ValueUnion(StackInt_t iVal);
+			explicit ValueUnion(const Common::String &strVal);
+			explicit ValueUnion(Common::String &&strVal);
+			~ValueUnion();
+		};
+
+		StackValue();
+		StackValue(const StackValue &other);
+		StackValue(StackValue &&other);
+		explicit StackValue(StackInt_t i);
+		explicit StackValue(const Common::String &str);
+		explicit StackValue(Common::String &&str);
+		~StackValue();
+
+		StackValue &operator=(const StackValue &other);
+		StackValue &operator=(StackValue &&other);
+
+		StackValueType type;
+		ValueUnion value;
+	};
 
 	bool runIdle();
 	bool runDelay();
@@ -475,7 +509,10 @@ private:
 
 	void loadIndex();
 	void findWaves();
-	void loadWave(uint soundID, const Common::String &soundName, const Common::ArchiveMemberPtr &archiveMemberPtr);
+	Common::SharedPtr<CachedSound> loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr);
+	void resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, CachedSound *&outWave);
+	void resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, CachedSound *&outWave);
+
 	void changeToScreen(uint roomNumber, uint screenNumber);
 	void returnToIdleState();
 	void changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor);
@@ -491,14 +528,14 @@ private:
 	void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
 	void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
 
-	void setSound3DParameters(uint soundID, int32 x, int32 y, const SoundParams3D &soundParams3D);
-	void triggerSound(bool looping, uint soundID, uint volume, int32 balance, bool is3D);
-	void triggerSoundRamp(uint soundID, uint durationMSec, uint newVolume, bool terminateOnCompletion);
+	void setSound3DParameters(CachedSound &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
+	void triggerSound(bool looping, CachedSound &sound, uint volume, int32 balance, bool is3D);
+	void triggerSoundRamp(CachedSound &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
 	void updateSounds(uint32 timestamp);
 	void update3DSounds();
 	bool computeEffectiveVolumeAndBalance(CachedSound &snd);
 
-	AnimationDef stackArgsToAnimDef(const StackValue_t *args) const;
+	AnimationDef stackArgsToAnimDef(const StackInt_t *args) const;
 	void pushAnimDef(const AnimationDef &animDef);
 
 	void activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars);
@@ -657,8 +694,8 @@ private:
 
 	uint _panCursors[kPanCursorMaxCount];
 
-	Common::HashMap<Common::String, StackValue_t> _namedCursors;
-	Common::HashMap<StackValue_t, uint> _scriptCursorIDToResourceIDOverride;
+	Common::HashMap<Common::String, StackInt_t> _namedCursors;
+	Common::HashMap<StackInt_t, uint> _scriptCursorIDToResourceIDOverride;
 
 	OSystem *_system;
 	uint _roomNumber;	// Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
@@ -708,7 +745,7 @@ private:
 
 	Common::SharedPtr<Script> _activeScript;
 	uint _scriptNextInstruction;
-	Common::Array<StackValue_t> _scriptStack;
+	Common::Array<StackValue> _scriptStack;
 	ScriptEnvironmentVars _scriptEnv;
 
 	Common::SharedPtr<Common::RandomSource> _rng;
@@ -769,7 +806,7 @@ private:
 	Common::Array<OSEvent> _pendingEvents;
 
 	Common::HashMap<Common::String, Common::ArchiveMemberPtr> _waves;
-	Common::HashMap<uint, Common::SharedPtr<CachedSound> > _cachedSounds;
+	Common::Array<Common::SharedPtr<CachedSound> > _activeSounds;
 	SoundParams3D _pendingSoundParams3D;
 
 	Common::Array<TriggeredOneShot> _triggeredOneShots;


Commit: 445de7972c3128370f19335feeec06afbfa5b3be
    https://github.com/scummvm/scummvm/commit/445de7972c3128370f19335feeec06afbfa5b3be
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-01T12:01:48-04:00

Commit Message:
VCRUISE: Refactor sound cache so sounds can be unloaded

Changed paths:
    engines/vcruise/runtime.cpp
    engines/vcruise/runtime.h


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 6049434a0ab..d8bbe3fb444 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -367,12 +367,7 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
 	}
 }
 
-CachedSound::CachedSound()
-	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
-	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), x(0), y(0), z(0) {
-}
-
-CachedSound::~CachedSound() {
+SoundCache::~SoundCache() {
 	// Dispose player first so playback stops
 	this->player.reset();
 
@@ -382,6 +377,14 @@ CachedSound::~CachedSound() {
 	this->stream.reset();
 }
 
+SoundInstance::SoundInstance()
+	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
+	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), x(0), y(0), z(0), evictTime(0) {
+}
+
+SoundInstance::~SoundInstance() {
+}
+
 TriggeredOneShot::TriggeredOneShot() : soundID(0), uniqueSlot(0) {
 }
 
@@ -427,7 +430,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
 	  /*_loadedArea(0), */_lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
 	  _delayCompletionTime(0),
 	  _panoramaState(kPanoramaStateInactive),
-	  _listenerX(0), _listenerY(0), _listenerAngle(0) {
+	  _listenerX(0), _listenerY(0), _listenerAngle(0), _soundCacheIndex(0) {
 
 	for (uint i = 0; i < kNumDirections; i++) {
 		_haveIdleAnimations[i] = false;
@@ -1437,8 +1440,8 @@ void Runtime::findWaves() {
 	}
 }
 
-Common::SharedPtr<CachedSound> Runtime::loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr) {
-	for (const Common::SharedPtr<CachedSound> &activeSound : _activeSounds) {
+Common::SharedPtr<SoundInstance> Runtime::loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr) {
+	for (const Common::SharedPtr<SoundInstance> &activeSound : _activeSounds) {
 		if (activeSound->name == soundName)
 			return activeSound;
 	}
@@ -1455,28 +1458,72 @@ Common::SharedPtr<CachedSound> Runtime::loadWave(const Common::String &soundName
 		return nullptr;
 	}
 
-	Common::SharedPtr<CachedSound> cachedSound(new CachedSound());
+	Common::SharedPtr<SoundInstance> soundInstance(new SoundInstance());
 
-	cachedSound->stream.reset(audioStream);
-	cachedSound->name = soundName;
-	cachedSound->id = soundID;
+	soundInstance->name = soundName;
+	soundInstance->id = soundID;
 
 	bool foundExisting = false;
-	for (Common::SharedPtr<CachedSound> &existingSound : _activeSounds) {
+	for (Common::SharedPtr<SoundInstance> &existingSound : _activeSounds) {
 		if (existingSound->id == soundID) {
-			existingSound = cachedSound;
+			existingSound = soundInstance;
 			foundExisting = true;
 			break;
 		}
 	}
 
 	if (!foundExisting)
-		_activeSounds.push_back(cachedSound);
+		_activeSounds.push_back(soundInstance);
+
+	return soundInstance;
+}
+
+SoundCache *Runtime::loadCache(SoundInstance &sound) {
+	if (sound.cache)
+		return sound.cache.get();
+
+	// See if this is already in the cache
+	for (const Common::Pair<Common::String, Common::SharedPtr<SoundCache> > &cacheItem : _soundCache) {
+		if (cacheItem.first == sound.name) {
+			sound.cache = cacheItem.second;
+			return sound.cache.get();
+		}
+	}
+
+	Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator waveIt = _waves.find(sound.name);
+
+	if (waveIt == _waves.end())
+		return nullptr;
+
+	Common::SeekableReadStream *stream = waveIt->_value->createReadStream();
+	if (!stream) {
+		warning("Couldn't open read stream for sound '%s'", sound.name.c_str());
+		return nullptr;
+	}
+
+	Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
+	if (!audioStream) {
+		warning("Couldn't open audio stream for sound '%s'", sound.name.c_str());
+		return nullptr;
+	}
+
+	Common::SharedPtr<SoundCache> cachedSound(new SoundCache());
+
+	cachedSound->stream.reset(audioStream);
+
+	_soundCache[_soundCacheIndex].first = sound.name;
+	_soundCache[_soundCacheIndex].second = cachedSound;
+
+	_soundCacheIndex++;
+	if (_soundCacheIndex == kSoundCacheSize)
+		_soundCacheIndex = 0;
 
-	return cachedSound;
+	sound.cache = cachedSound;
+
+	return cachedSound.get();
 }
 
-void Runtime::resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, CachedSound *&outWave) {
+void Runtime::resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, SoundInstance *&outWave) {
 	Common::String sndName = soundName;
 
 	uint soundID = 0;
@@ -1488,7 +1535,7 @@ void Runtime::resolveSoundByName(const Common::String &soundName, StackInt_t &ou
 	outSoundID = soundID;
 	outWave = nullptr;
 
-	for (const Common::SharedPtr<CachedSound> &snd : _activeSounds) {
+	for (const Common::SharedPtr<SoundInstance> &snd : _activeSounds) {
 		if (snd->name == sndName) {
 			outWave = snd.get();
 			return;
@@ -1498,19 +1545,19 @@ void Runtime::resolveSoundByName(const Common::String &soundName, StackInt_t &ou
 	Common::HashMap<Common::String, Common::ArchiveMemberPtr>::const_iterator waveIt = _waves.find(sndName);
 
 	if (waveIt != _waves.end()) {
-		Common::SharedPtr<CachedSound> snd = loadWave(sndName, soundID, waveIt->_value);
+		Common::SharedPtr<SoundInstance> snd = loadWave(sndName, soundID, waveIt->_value);
 		outWave = snd.get();
 	}
 }
 
-void Runtime::resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, CachedSound *&outWave) {
+void Runtime::resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, SoundInstance *&outWave) {
 	outSoundID = 0;
 	outWave = nullptr;
 
 	if (stackValue.type == StackValue::kNumber) {
 		outSoundID = stackValue.value.i;
 
-		for (const Common::SharedPtr<CachedSound> &snd : _activeSounds) {
+		for (const Common::SharedPtr<SoundInstance> &snd : _activeSounds) {
 			if (snd->id == static_cast<uint>(stackValue.value.i)) {
 				outWave = snd.get();
 				break;
@@ -1983,50 +2030,57 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
 	debug(1, "Animation last frame set to %u", animDef.lastFrame);
 }
 
-void Runtime::setSound3DParameters(CachedSound &snd, int32 x, int32 y, const SoundParams3D &soundParams3D) {
+void Runtime::setSound3DParameters(SoundInstance &snd, int32 x, int32 y, const SoundParams3D &soundParams3D) {
 	snd.x = x;
 	snd.y = y;
 	snd.params3D = soundParams3D;
 }
 
-void Runtime::triggerSound(bool looping, CachedSound &snd, uint volume, int32 balance, bool is3D) {
+void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32 balance, bool is3D) {
 	snd.volume = volume;
 	snd.balance = balance;
 	snd.is3D = is3D;
 
 	computeEffectiveVolumeAndBalance(snd);
 
+	SoundCache *cache = loadCache(snd);
+
 	// Reset if looping state changes
-	if (snd.loopingStream && !looping) {
-		snd.player.reset();
-		snd.loopingStream.reset();
-		snd.stream->rewind();
+	if (cache->loopingStream && !looping) {
+		cache->player.reset();
+		cache->loopingStream.reset();
+		cache->stream->rewind();
 	}
 
-	if (!snd.loopingStream && looping)
-		snd.player.reset();
+	if (!cache->loopingStream && looping)
+		cache->player.reset();
 
 	// Construct looping stream if needed and none exists
-	if (looping && !snd.loopingStream)
-		snd.loopingStream.reset(new Audio::LoopingAudioStream(snd.stream.get(), 0, DisposeAfterUse::NO, true));
+	if (looping && !cache->loopingStream)
+		cache->loopingStream.reset(new Audio::LoopingAudioStream(cache->stream.get(), 0, DisposeAfterUse::NO, true));
 
-	if (snd.player) {
+	if (cache->player) {
 		// If there is already a player and this is non-looping, start over
 		if (!looping) {
-			snd.player->stop();
-			snd.stream->rewind();
-			snd.player->play(snd.effectiveVolume, snd.effectiveBalance);
+			cache->player->stop();
+			cache->stream->rewind();
+			cache->player->play(snd.effectiveVolume, snd.effectiveBalance);
 		} else {
 			// Adjust volume and balance at least
-			snd.player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
+			cache->player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
 		}
 	} else {
-		snd.player.reset(new AudioPlayer(_mixer, looping ? snd.loopingStream.staticCast<Audio::AudioStream>() : snd.stream.staticCast<Audio::AudioStream>()));
-		snd.player->play(snd.effectiveVolume, snd.effectiveBalance);
+		cache->player.reset(new AudioPlayer(_mixer, looping ? cache->loopingStream.staticCast<Audio::AudioStream>() : cache->stream.staticCast<Audio::AudioStream>()));
+		cache->player->play(snd.effectiveVolume, snd.effectiveBalance);
 	}
+
+	if (looping)
+		snd.evictTime = 0;
+	else
+		snd.evictTime = g_system->getMillis(true) + static_cast<uint32>(cache->stream->getLength().msecs()) + 20000u;
 }
 
-void Runtime::triggerSoundRamp(CachedSound &snd, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
+void Runtime::triggerSoundRamp(SoundInstance &snd, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
 	snd.rampStartVolume = snd.volume;
 	snd.rampEndVolume = newVolume;
 	snd.rampTerminateOnCompletion = terminateOnCompletion;
@@ -2037,11 +2091,20 @@ void Runtime::triggerSoundRamp(CachedSound &snd, uint durationMSec, uint newVolu
 		snd.rampRatePerMSec = 65536 / durationMSec;
 }
 
-void Runtime::updateSounds(uint32 timestamp) {
-	Common::Array<uint> condemnedSounds;
+void Runtime::stopSound(SoundInstance &sound) {
+	if (!sound.cache)
+		return;
+
+	if (sound.cache->player)
+		sound.cache->player->stop();
+
+	sound.cache.reset();
+	sound.evictTime = g_system->getMillis(true) + 20000u;
+}
 
+void Runtime::updateSounds(uint32 timestamp) {
 	for (uint sndIndex = 0; sndIndex < _activeSounds.size(); sndIndex++) {
-		CachedSound &snd = *_activeSounds[sndIndex];
+		SoundInstance &snd = *_activeSounds[sndIndex];
 
 		if (snd.rampRatePerMSec) {
 			uint ramp = snd.rampRatePerMSec * (timestamp - snd.rampStartTime);
@@ -2050,7 +2113,7 @@ void Runtime::updateSounds(uint32 timestamp) {
 				snd.rampRatePerMSec = 0;
 				newVolume = snd.rampEndVolume;
 				if (snd.rampTerminateOnCompletion)
-					condemnedSounds.push_back(sndIndex);
+					stopSound(snd);
 			} else {
 				uint rampedVolume = (snd.rampStartVolume * (65536u - ramp)) + (snd.rampEndVolume * ramp);
 				newVolume = rampedVolume >> 16;
@@ -2059,27 +2122,26 @@ void Runtime::updateSounds(uint32 timestamp) {
 			if (snd.volume != newVolume) {
 				snd.volume = newVolume;
 
-				if (snd.player) {
-					computeEffectiveVolumeAndBalance(snd);
-					snd.player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
+				if (snd.cache) {
+					SoundCache *cache = snd.cache.get();
+					if (cache->player) {
+						computeEffectiveVolumeAndBalance(snd);
+						cache->player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
+					}
 				}
 			}
 		}
-	}
 
-	// condemnedSounds must be sorted
-	if (condemnedSounds.size()) {
-		uint numDeleted = 0;
-		for (uint index : condemnedSounds) {
-			_activeSounds.remove_at(index - numDeleted);
-			numDeleted++;
+		if (snd.evictTime && snd.evictTime <= timestamp) {
+			snd.cache.reset();
+			snd.evictTime = 0;
 		}
 	}
 }
 
 void Runtime::update3DSounds() {
-	for (const Common::SharedPtr<CachedSound> &sndPtr : _activeSounds) {
-		CachedSound &snd = *sndPtr;
+	for (const Common::SharedPtr<SoundInstance> &sndPtr : _activeSounds) {
+		SoundInstance &snd = *sndPtr;
 
 		if (!snd.is3D)
 			continue;
@@ -2087,14 +2149,19 @@ void Runtime::update3DSounds() {
 		bool changed = computeEffectiveVolumeAndBalance(snd);
 
 		if (changed) {
-			VCruise::AudioPlayer *player = snd.player.get();
-			if (player)
-				player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
+			if (snd.cache) {
+				SoundCache *cache = snd.cache.get();
+				if (cache) {
+					VCruise::AudioPlayer *player = cache->player.get();
+					if (player)
+						player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
+				}
+			}
 		}
 	}
 }
 
-bool Runtime::computeEffectiveVolumeAndBalance(CachedSound &snd) {
+bool Runtime::computeEffectiveVolumeAndBalance(SoundInstance &snd) {
 	uint effectiveVolume = snd.volume;
 	int32 effectiveBalance = snd.balance;
 
@@ -3194,7 +3261,7 @@ void Runtime::scriptOpSoundS1(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3206,7 +3273,7 @@ void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3218,7 +3285,7 @@ void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3229,7 +3296,7 @@ void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3241,7 +3308,7 @@ void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3253,7 +3320,7 @@ void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3265,7 +3332,7 @@ void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound) {
@@ -3279,7 +3346,7 @@ void Runtime::scriptOp3DSoundL3(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound) {
@@ -3293,7 +3360,7 @@ void Runtime::scriptOp3DSoundS2(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound) {
@@ -3460,7 +3527,7 @@ void Runtime::scriptOpVolumeUp3(ScriptArg_t arg) {
 	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3472,7 +3539,7 @@ void Runtime::scriptOpVolumeDn2(ScriptArg_t arg) {
 	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
 	// FIXME: Just do this instantly
@@ -3485,7 +3552,7 @@ void Runtime::scriptOpVolumeDn3(ScriptArg_t arg) {
 	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3497,7 +3564,7 @@ void Runtime::scriptOpVolumeDn4(ScriptArg_t arg) {
 	TAKE_STACK_VAR_NAMED(1, sndIDArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByNameOrID(sndIDArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
@@ -3539,7 +3606,7 @@ void Runtime::scriptOpSay1(ScriptArg_t arg) {
 	warning("Say1 cycles are not implemented yet, playing first sound in the cycle");
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	// uint ? = sndParamArgs[0];
@@ -3554,7 +3621,7 @@ void Runtime::scriptOpSay3(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound) {
@@ -3578,7 +3645,7 @@ void Runtime::scriptOpSay3Get(ScriptArg_t arg) {
 	TAKE_STACK_STR_NAMED(1, sndNameArgs);
 
 	StackInt_t soundID = 0;
-	CachedSound *cachedSound = nullptr;
+	SoundInstance *cachedSound = nullptr;
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound) {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 452fc691e86..c784cc45110 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -186,14 +186,20 @@ struct SoundParams3D {
 	uint unknownRange;
 };
 
-struct CachedSound {
-	CachedSound();
-	~CachedSound();
+struct SoundCache {
+	~SoundCache();
 
-	Common::String name;
 	Common::SharedPtr<Audio::SeekableAudioStream> stream;
 	Common::SharedPtr<Audio::AudioStream> loopingStream;
 	Common::SharedPtr<AudioPlayer> player;
+};
+
+struct SoundInstance {
+	SoundInstance();
+	~SoundInstance();
+
+	Common::String name;
+	Common::SharedPtr<SoundCache> cache;
 
 	uint id;
 
@@ -215,6 +221,8 @@ struct CachedSound {
 	int32 z;
 
 	SoundParams3D params3D;
+
+	uint32 evictTime;
 };
 
 struct TriggeredOneShot {
@@ -509,9 +517,10 @@ private:
 
 	void loadIndex();
 	void findWaves();
-	Common::SharedPtr<CachedSound> loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr);
-	void resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, CachedSound *&outWave);
-	void resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, CachedSound *&outWave);
+	Common::SharedPtr<SoundInstance> loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr);
+	SoundCache *loadCache(SoundInstance &sound);
+	void resolveSoundByName(const Common::String &soundName, StackInt_t &outSoundID, SoundInstance *&outWave);
+	void resolveSoundByNameOrID(const StackValue &stackValue, StackInt_t &outSoundID, SoundInstance *&outWave);
 
 	void changeToScreen(uint roomNumber, uint screenNumber);
 	void returnToIdleState();
@@ -528,12 +537,13 @@ private:
 	void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
 	void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
 
-	void setSound3DParameters(CachedSound &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
-	void triggerSound(bool looping, CachedSound &sound, uint volume, int32 balance, bool is3D);
-	void triggerSoundRamp(CachedSound &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
+	void setSound3DParameters(SoundInstance &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
+	void triggerSound(bool looping, SoundInstance &sound, uint volume, int32 balance, bool is3D);
+	void triggerSoundRamp(SoundInstance &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
+	void stopSound(SoundInstance &sound);
 	void updateSounds(uint32 timestamp);
 	void update3DSounds();
-	bool computeEffectiveVolumeAndBalance(CachedSound &snd);
+	bool computeEffectiveVolumeAndBalance(SoundInstance &snd);
 
 	AnimationDef stackArgsToAnimDef(const StackInt_t *args) const;
 	void pushAnimDef(const AnimationDef &animDef);
@@ -806,7 +816,7 @@ private:
 	Common::Array<OSEvent> _pendingEvents;
 
 	Common::HashMap<Common::String, Common::ArchiveMemberPtr> _waves;
-	Common::Array<Common::SharedPtr<CachedSound> > _activeSounds;
+	Common::Array<Common::SharedPtr<SoundInstance> > _activeSounds;
 	SoundParams3D _pendingSoundParams3D;
 
 	Common::Array<TriggeredOneShot> _triggeredOneShots;
@@ -829,6 +839,11 @@ private:
 	static const uint kSaveGameIdentifier = 0x53566372;
 	static const uint kSaveGameCurrentVersion = 2;
 	static const uint kSaveGameEarliestSupportedVersion = 2;
+
+	static const uint kSoundCacheSize = 16;
+
+	Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
+	uint _soundCacheIndex;
 };
 
 } // End of namespace VCruise


Commit: c3154d5299a0cc2fab4191bba1a263e843a607c8
    https://github.com/scummvm/scummvm/commit/c3154d5299a0cc2fab4191bba1a263e843a607c8
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-01T12:01:48-04:00

Commit Message:
VCRUISE: Keep track of loop state, evict ended sounds.

Changed paths:
    engines/vcruise/runtime.cpp
    engines/vcruise/runtime.h


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index d8bbe3fb444..fd5bbe1248a 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -379,7 +379,7 @@ SoundCache::~SoundCache() {
 
 SoundInstance::SoundInstance()
 	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
-	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), x(0), y(0), z(0), evictTime(0) {
+	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), x(0), y(0), z(0), endTime(0) {
 }
 
 SoundInstance::~SoundInstance() {
@@ -2040,6 +2040,7 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
 	snd.volume = volume;
 	snd.balance = balance;
 	snd.is3D = is3D;
+	snd.isLooping = looping;
 
 	computeEffectiveVolumeAndBalance(snd);
 
@@ -2075,9 +2076,9 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
 	}
 
 	if (looping)
-		snd.evictTime = 0;
+		snd.endTime = 0;
 	else
-		snd.evictTime = g_system->getMillis(true) + static_cast<uint32>(cache->stream->getLength().msecs()) + 20000u;
+		snd.endTime = g_system->getMillis(true) + static_cast<uint32>(cache->stream->getLength().msecs()) + 1000u;
 }
 
 void Runtime::triggerSoundRamp(SoundInstance &snd, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
@@ -2099,7 +2100,7 @@ void Runtime::stopSound(SoundInstance &sound) {
 		sound.cache->player->stop();
 
 	sound.cache.reset();
-	sound.evictTime = g_system->getMillis(true) + 20000u;
+	sound.endTime = 0;
 }
 
 void Runtime::updateSounds(uint32 timestamp) {
@@ -2132,9 +2133,10 @@ void Runtime::updateSounds(uint32 timestamp) {
 			}
 		}
 
-		if (snd.evictTime && snd.evictTime <= timestamp) {
+		// Cache-evict stopped sounds
+		if (snd.endTime && snd.endTime <= timestamp) {
 			snd.cache.reset();
-			snd.evictTime = 0;
+			snd.endTime = 0;
 		}
 	}
 }
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index c784cc45110..7bde55c90d5 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -216,13 +216,14 @@ struct SoundInstance {
 	int32 effectiveBalance;
 
 	bool is3D;
+	bool isLooping;
 	int32 x;
 	int32 y;
 	int32 z;
 
 	SoundParams3D params3D;
 
-	uint32 evictTime;
+	uint32 endTime;
 };
 
 struct TriggeredOneShot {


Commit: 7f549ce5c52572acc2a36dde2b50e7797dcb7a10
    https://github.com/scummvm/scummvm/commit/7f549ce5c52572acc2a36dde2b50e7797dcb7a10
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-01T12:01:48-04:00

Commit Message:
VCRUISE: Hook up volume controls.

Changed paths:
    engines/vcruise/audio_player.cpp
    engines/vcruise/audio_player.h
    engines/vcruise/runtime.cpp
    engines/vcruise/runtime.h


diff --git a/engines/vcruise/audio_player.cpp b/engines/vcruise/audio_player.cpp
index abe704aa929..65957de5e5f 100644
--- a/engines/vcruise/audio_player.cpp
+++ b/engines/vcruise/audio_player.cpp
@@ -23,8 +23,8 @@
 
 namespace VCruise {
 
-AudioPlayer::AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream)
-	: _exhausted(false), _isPlaying(false), _mixer(mixer), _baseStream(baseStream) {
+AudioPlayer::AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream, Audio::Mixer::SoundType soundType)
+	: _exhausted(false), _isPlaying(false), _mixer(mixer), _baseStream(baseStream), _soundType(soundType) {
 }
 
 AudioPlayer::~AudioPlayer() {
@@ -62,7 +62,7 @@ void AudioPlayer::play(byte volume, int8 balance) {
 	if (!_isPlaying) {
 		_isPlaying = true;
 		_exhausted = false;
-		_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, volume, balance, DisposeAfterUse::NO);
+		_mixer->playStream(_soundType, &_handle, this, -1, volume, balance, DisposeAfterUse::NO);
 	}
 
 }
diff --git a/engines/vcruise/audio_player.h b/engines/vcruise/audio_player.h
index feac26907e1..a7115b29d93 100644
--- a/engines/vcruise/audio_player.h
+++ b/engines/vcruise/audio_player.h
@@ -34,7 +34,7 @@ class CachedAudio;
 
 class AudioPlayer : public Audio::AudioStream {
 public:
-	AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream);
+	AudioPlayer(Audio::Mixer *mixer, const Common::SharedPtr<Audio::AudioStream> &baseStream, Audio::Mixer::SoundType soundType);
 	~AudioPlayer();
 
 	int readBuffer(int16 *buffer, const int numSamples) override;
@@ -58,6 +58,8 @@ private:
 	bool _exhausted;
 	Audio::Mixer *_mixer;
 	Common::SharedPtr<Audio::AudioStream> _baseStream;
+
+	Audio::Mixer::SoundType _soundType;
 };
 
 } // End of namespace VCruise
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index fd5bbe1248a..f5af7e25feb 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -275,7 +275,7 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
 
 			sample->memoryStream.reset(new Common::MemoryReadStream(&sample->soundData[0], static_cast<uint32>(size)));
 			sample->audioStream.reset(Audio::makeWAVStream(sample->memoryStream.get(), DisposeAfterUse::NO));
-			sample->audioPlayer.reset(new AudioPlayer(mixer, sample->audioStream));
+			sample->audioPlayer.reset(new AudioPlayer(mixer, sample->audioStream, Audio::Mixer::kSFXSoundType));
 
 			this->sounds[keyValue.key] = sample;
 		}
@@ -379,7 +379,7 @@ SoundCache::~SoundCache() {
 
 SoundInstance::SoundInstance()
 	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
-	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), x(0), y(0), z(0), endTime(0) {
+	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), isSpeech(false), x(0), y(0), z(0), endTime(0) {
 }
 
 SoundInstance::~SoundInstance() {
@@ -1924,7 +1924,7 @@ void Runtime::changeMusicTrack(int track) {
 		if (Audio::SeekableAudioStream *audioStream = Audio::makeWAVStream(wavFile, DisposeAfterUse::YES)) {
 			Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
 
-			_musicPlayer.reset(new AudioPlayer(_mixer, loopingStream));
+			_musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, Audio::Mixer::kMusicSoundType));
 			_musicPlayer->play(100, 0);
 		}
 	} else {
@@ -2036,11 +2036,12 @@ void Runtime::setSound3DParameters(SoundInstance &snd, int32 x, int32 y, const S
 	snd.params3D = soundParams3D;
 }
 
-void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32 balance, bool is3D) {
+void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32 balance, bool is3D, bool isSpeech) {
 	snd.volume = volume;
 	snd.balance = balance;
 	snd.is3D = is3D;
 	snd.isLooping = looping;
+	snd.isSpeech = isSpeech;
 
 	computeEffectiveVolumeAndBalance(snd);
 
@@ -2060,6 +2061,8 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
 	if (looping && !cache->loopingStream)
 		cache->loopingStream.reset(new Audio::LoopingAudioStream(cache->stream.get(), 0, DisposeAfterUse::NO, true));
 
+	const Audio::Mixer::SoundType soundType = (isSpeech ? Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType);
+
 	if (cache->player) {
 		// If there is already a player and this is non-looping, start over
 		if (!looping) {
@@ -2071,7 +2074,7 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
 			cache->player->setVolumeAndBalance(snd.effectiveVolume, snd.effectiveBalance);
 		}
 	} else {
-		cache->player.reset(new AudioPlayer(_mixer, looping ? cache->loopingStream.staticCast<Audio::AudioStream>() : cache->stream.staticCast<Audio::AudioStream>()));
+		cache->player.reset(new AudioPlayer(_mixer, looping ? cache->loopingStream.staticCast<Audio::AudioStream>() : cache->stream.staticCast<Audio::AudioStream>(), soundType));
 		cache->player->play(snd.effectiveVolume, snd.effectiveBalance);
 	}
 
@@ -3267,7 +3270,7 @@ void Runtime::scriptOpSoundS1(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(false, *cachedSound, 100, 0, false);
+		triggerSound(false, *cachedSound, 100, 0, false, false);
 }
 
 void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
@@ -3279,7 +3282,7 @@ void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(false, *cachedSound, sndParamArgs[0], 0, false);
+		triggerSound(false, *cachedSound, sndParamArgs[0], 0, false, false);
 }
 
 void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
@@ -3291,7 +3294,7 @@ void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(false, *cachedSound, sndParamArgs[0], sndParamArgs[1], false);
+		triggerSound(false, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
 }
 
 void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
@@ -3302,7 +3305,7 @@ void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(true, *cachedSound, 100, 0, false);
+		triggerSound(true, *cachedSound, 100, 0, false, false);
 }
 
 void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
@@ -3314,7 +3317,7 @@ void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(true, *cachedSound, sndParamArgs[0], 0, false);
+		triggerSound(true, *cachedSound, sndParamArgs[0], 0, false, false);
 }
 
 void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
@@ -3326,7 +3329,7 @@ void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], soundID, cachedSound);
 
 	if (cachedSound)
-		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], false);
+		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
 }
 
 void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
@@ -3339,7 +3342,7 @@ void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
 
 	if (cachedSound) {
 		setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
-		triggerSound(true, *cachedSound, sndParamArgs[0], 0, true);
+		triggerSound(true, *cachedSound, sndParamArgs[0], 0, true, false);
 	}
 }
 
@@ -3353,7 +3356,7 @@ void Runtime::scriptOp3DSoundL3(ScriptArg_t arg) {
 
 	if (cachedSound) {
 		setSound3DParameters(*cachedSound, sndParamArgs[2], sndParamArgs[3], _pendingSoundParams3D);
-		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], true);
+		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], true, false);
 	}
 }
 
@@ -3367,7 +3370,7 @@ void Runtime::scriptOp3DSoundS2(ScriptArg_t arg) {
 
 	if (cachedSound) {
 		setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
-		triggerSound(false, *cachedSound, sndParamArgs[0], 0, true);
+		triggerSound(false, *cachedSound, sndParamArgs[0], 0, true, false);
 	}
 }
 
@@ -3615,7 +3618,7 @@ void Runtime::scriptOpSay1(ScriptArg_t arg) {
 	// uint cycleLength = sndParamArgs[1];
 
 	if (cachedSound)
-		triggerSound(false, *cachedSound, 100, 0, false);
+		triggerSound(false, *cachedSound, 100, 0, false, true);
 }
 
 void Runtime::scriptOpSay3(ScriptArg_t arg) {
@@ -3636,7 +3639,7 @@ void Runtime::scriptOpSay3(ScriptArg_t arg) {
 			error("Invalid interrupt arg for say3, only 1 is supported.");
 
 		if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
-			triggerSound(false, *cachedSound, 100, 0, false);
+			triggerSound(false, *cachedSound, 100, 0, false, true);
 			_triggeredOneShots.push_back(oneShot);
 		}
 	}
@@ -3660,7 +3663,7 @@ void Runtime::scriptOpSay3Get(ScriptArg_t arg) {
 			error("Invalid interrupt arg for say3, only 1 is supported.");
 
 		if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
-			triggerSound(false, *cachedSound, 100, 0, false);
+			triggerSound(false, *cachedSound, 100, 0, false, true);
 			_triggeredOneShots.push_back(oneShot);
 			_scriptStack.push_back(StackValue(soundID));
 		} else
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 7bde55c90d5..67cfc8ec66d 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -217,6 +217,7 @@ struct SoundInstance {
 
 	bool is3D;
 	bool isLooping;
+	bool isSpeech;
 	int32 x;
 	int32 y;
 	int32 z;
@@ -539,7 +540,7 @@ private:
 	void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
 
 	void setSound3DParameters(SoundInstance &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
-	void triggerSound(bool looping, SoundInstance &sound, uint volume, int32 balance, bool is3D);
+	void triggerSound(bool looping, SoundInstance &sound, uint volume, int32 balance, bool is3D, bool isSpeech);
 	void triggerSoundRamp(SoundInstance &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
 	void stopSound(SoundInstance &sound);
 	void updateSounds(uint32 timestamp);


Commit: 3bb0094d7d2269152c031f8ea462fc262bab5ffb
    https://github.com/scummvm/scummvm/commit/3bb0094d7d2269152c031f8ea462fc262bab5ffb
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-01T12:01:48-04:00

Commit Message:
VCRUISE: Save format rework

Changed paths:
    engines/vcruise/runtime.cpp
    engines/vcruise/runtime.h
    engines/vcruise/vcruise.cpp


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index f5af7e25feb..cd0658585dc 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -379,7 +379,7 @@ SoundCache::~SoundCache() {
 
 SoundInstance::SoundInstance()
 	: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
-	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), isSpeech(false), x(0), y(0), z(0), endTime(0) {
+	  volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), isSpeech(false), x(0), y(0), endTime(0) {
 }
 
 SoundInstance::~SoundInstance() {
@@ -396,9 +396,31 @@ bool TriggeredOneShot::operator!=(const TriggeredOneShot &other) const {
 	return !((*this) == other);
 }
 
+void TriggeredOneShot::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(soundID);
+	stream->writeUint32BE(uniqueSlot);
+}
+
+void TriggeredOneShot::read(Common::ReadStream *stream) {
+	soundID = stream->readUint32BE();
+	uniqueSlot = stream->readUint32BE();
+}
+
 StaticAnimParams::StaticAnimParams() : initialDelay(0), repeatDelay(0), lockInteractions(false) {
 }
 
+void StaticAnimParams::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(initialDelay);
+	stream->writeUint32BE(repeatDelay);
+	stream->writeByte(lockInteractions ? 1 : 0);
+}
+
+void StaticAnimParams::read(Common::ReadStream *stream) {
+	initialDelay = stream->readUint32BE();
+	repeatDelay = stream->readUint32BE();
+	lockInteractions = (stream->readByte() != 0);
+}
+
 StaticAnimation::StaticAnimation() : currentAlternation(0), nextStartTime(0) {
 }
 
@@ -411,6 +433,18 @@ FrameData2::FrameData2() : x(0), y(0), angle(0), frameNumberInArea(0), unknown(0
 SoundParams3D::SoundParams3D() : minRange(0), maxRange(0), unknownRange(0) {
 }
 
+void SoundParams3D::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(minRange);
+	stream->writeUint32BE(maxRange);
+	stream->writeUint32BE(unknownRange);
+}
+
+void SoundParams3D::read(Common::ReadStream *stream) {
+	minRange = stream->readUint32BE();
+	maxRange = stream->readUint32BE();
+	unknownRange = stream->readUint32BE();
+}
+
 InventoryItem::InventoryItem() : itemID(0), highlighted(false) {
 }
 
@@ -420,10 +454,195 @@ Fraction::Fraction() : numerator(0), denominator(1) {
 Fraction::Fraction(uint pNumerator, uint pDenominator) : numerator(pNumerator), denominator(pDenominator) {
 }
 
+SaveGameSnapshot::InventoryItem::InventoryItem() : itemID(0), highlighted(false) {
+}
+
+void SaveGameSnapshot::InventoryItem::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(itemID);
+	stream->writeByte(highlighted ? 1 : 0);
+}
+
+void SaveGameSnapshot::InventoryItem::read(Common::ReadStream *stream) {
+	itemID = stream->readUint32BE();
+	highlighted = (stream->readByte() != 0);
+}
+
+SaveGameSnapshot::Sound::Sound() : id(0), volume(0), balance(0), is3D(false), isLooping(false), isSpeech(false), x(0), y(0) {
+}
+
+void SaveGameSnapshot::Sound::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(name.size());
+	stream->writeString(name);
+
+	stream->writeUint32BE(id);
+	stream->writeUint32BE(volume);
+	stream->writeSint32BE(balance);
+
+	stream->writeByte(is3D ? 1 : 0);
+	stream->writeByte(isLooping ? 1 : 0);
+	stream->writeByte(isSpeech ? 1 : 0);
+
+	stream->writeSint32BE(x);
+	stream->writeSint32BE(y);
+
+	params3D.write(stream);
+}
+
+void SaveGameSnapshot::Sound::read(Common::ReadStream *stream) {
+	uint nameLen = stream->readUint32BE();
+
+	if (stream->eos() || stream->err() || nameLen > 256)
+		nameLen = 0;
+
+	name = stream->readString(0, nameLen);
+
+	id = stream->readUint32BE();
+	volume = stream->readUint32BE();
+	balance = stream->readSint32BE();
+
+	is3D = (stream->readByte() != 0);
+	isLooping = (stream->readByte() != 0);
+	isSpeech = (stream->readByte() != 0);
+
+	x = stream->readSint32BE();
+	y = stream->readSint32BE();
+
+	params3D.read(stream);
+}
+
+SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), loadedAnimation(0),
+									   animDisplayingFrame(0), listenerX(0), listenerY(0), listenerAngle(0) {
+}
+
+void SaveGameSnapshot::write(Common::WriteStream *stream) const {
+	stream->writeUint32BE(kSaveGameIdentifier);
+	stream->writeUint32BE(kSaveGameCurrentVersion);
+
+	stream->writeUint32BE(roomNumber);
+	stream->writeUint32BE(screenNumber);
+	stream->writeUint32BE(direction);
+
+	stream->writeByte(escOn ? 1 : 0);
+	stream->writeSint32BE(musicTrack);
+
+	stream->writeUint32BE(loadedAnimation);
+	stream->writeUint32BE(animDisplayingFrame);
+
+	pendingStaticAnimParams.write(stream);
+	pendingSoundParams3D.write(stream);
+
+	stream->writeSint32BE(listenerX);
+	stream->writeSint32BE(listenerY);
+	stream->writeSint32BE(listenerAngle);
+
+	stream->writeUint32BE(inventory.size());
+	stream->writeUint32BE(sounds.size());
+	stream->writeUint32BE(triggeredOneShots.size());
+
+	stream->writeUint32BE(variables.size());
+	stream->writeUint32BE(timers.size());
+
+	for (const InventoryItem &invItem : inventory)
+		invItem.write(stream);
+
+	for (const Sound &sound : sounds)
+		sound.write(stream);
+
+	for (const TriggeredOneShot &triggeredOneShot : triggeredOneShots)
+		triggeredOneShot.write(stream);
+
+	for (const Common::HashMap<uint32, int32>::Node &var : variables) {
+		stream->writeUint32BE(var._key);
+		stream->writeSint32BE(var._value);
+	}
+
+	for (const Common::HashMap<uint, uint32>::Node &timer : timers) {
+		stream->writeUint32BE(timer._key);
+		stream->writeUint32BE(timer._value);
+	}
+}
+
+LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
+	uint32 saveIdentifier = stream->readUint32BE();
+	uint32 saveVersion = stream->readUint32BE();
+
+	if (stream->eos() || stream->err())
+		return kLoadGameOutcomeMissingVersion;
+
+	if (saveIdentifier != kSaveGameIdentifier)
+		return kLoadGameOutcomeInvalidVersion;
+
+	if (saveVersion > kSaveGameCurrentVersion)
+		return kLoadGameOutcomeSaveIsTooNew;
+
+	if (saveVersion < kSaveGameEarliestSupportedVersion)
+		return kLoadGameOutcomeSaveIsTooOld;
+
+	
+	roomNumber = stream->readUint32BE();
+	screenNumber = stream->readUint32BE();
+	direction = stream->readUint32BE();
+
+	escOn = (stream->readByte() != 0);
+	musicTrack = stream->readSint32BE();
+
+	loadedAnimation = stream->readUint32BE();
+	animDisplayingFrame = stream->readUint32BE();
+
+	pendingStaticAnimParams.read(stream);
+	pendingSoundParams3D.read(stream);
+
+	listenerX = stream->readSint32BE();
+	listenerY = stream->readSint32BE();
+	listenerAngle = stream->readSint32BE();
+
+	uint numInventory = stream->readUint32BE();
+	uint numSounds = stream->readUint32BE();
+	uint numOneShots = stream->readUint32BE();
+
+	uint numVars = stream->readUint32BE();
+	uint numTimers = stream->readUint32BE();
+
+	if (stream->eos() || stream->err())
+		return kLoadGameOutcomeSaveDataCorrupted;
+
+	inventory.resize(numInventory);
+	sounds.resize(numSounds);
+	triggeredOneShots.resize(numOneShots);
+
+	for (uint i = 0; i < numInventory; i++)
+		inventory[i].read(stream);
+
+	for (uint i = 0; i < numSounds; i++)
+		sounds[i].read(stream);
+
+	for (uint i = 0; i < numOneShots; i++)
+		triggeredOneShots[i].read(stream);
+
+	for (uint i = 0; i < numVars; i++) {
+		uint32 key = stream->readUint32BE();
+		int32 value = stream->readSint32BE();
+
+		variables[key] = value;
+	}
+
+	for (uint i = 0; i < numTimers; i++) {
+		uint32 key = stream->readUint32BE();
+		uint32 value = stream->readUint32BE();
+
+		timers[key] = value;
+	}
+
+	if (stream->eos() || stream->err())
+		return kLoadGameOutcomeSaveDataCorrupted;
+
+	return kLoadGameOutcomeSucceeded;
+}
+
 Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
 	: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
 	  _gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
-	  _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _panoramaDirectionFlags(0),
+	  _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _musicTrack(0), _panoramaDirectionFlags(0),
 	  _loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
 	  _animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
 	  _animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
@@ -1633,6 +1852,8 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
 
 		_havePendingReturnToIdleState = true;
 		_haveIdleStaticAnimation = false;
+
+		recordSaveGameSnapshot();
 	}
 }
 
@@ -1916,7 +2137,11 @@ void Runtime::loadFrameData2(Common::SeekableReadStream *stream) {
 }
 
 void Runtime::changeMusicTrack(int track) {
+	if (track == _musicTrack)
+		return;
+
 	_musicPlayer.reset();
+	_musicTrack = track;
 
 	Common::String wavFileName = Common::String::format("Sfx/Music-%02i.wav", static_cast<int>(track));
 	Common::File *wavFile = new Common::File();
@@ -2652,136 +2877,185 @@ void Runtime::onKeyDown(Common::KeyCode keyCode) {
 }
 
 bool Runtime::canSave() const {
-	return _gameState == kGameStateIdle;
+	return !!_saveGame;
 }
 
 bool Runtime::canLoad() const {
 	return _gameState == kGameStateIdle;
 }
 
-void Runtime::saveGame(Common::WriteStream *stream) const {
-	stream->writeUint32BE(kSaveGameIdentifier);
-	stream->writeUint32BE(kSaveGameCurrentVersion);
+void Runtime::recordSaveGameSnapshot() {
+	_saveGame.reset();
 
-	stream->writeUint32BE(_roomNumber);
-	stream->writeUint32BE(_screenNumber);
-	stream->writeUint32BE(_direction);
+	uint32 timeBase = g_system->getMillis();
 
-	Common::Array<uint32> variableIDs;
-	Common::Array<uint> timerIDs;
+	Common::SharedPtr<SaveGameSnapshot> snapshot(new SaveGameSnapshot());
 
-	uint32 timeBase = g_system->getMillis();
+	_saveGame = snapshot;
+
+	for (const InventoryItem &inventoryItem : _inventory) {
+		SaveGameSnapshot::InventoryItem saveItem;
+		saveItem.itemID = inventoryItem.itemID;
+		saveItem.highlighted = inventoryItem.highlighted;
+
+		snapshot->inventory.push_back(saveItem);
+	}
 
-	for (const Common::HashMap<uint32, int32>::Node &varNode : _variables)
-		variableIDs.push_back(varNode._key);
+	snapshot->roomNumber = _roomNumber;
+	snapshot->screenNumber = _screenNumber;
+	snapshot->direction = _direction;
+
+	snapshot->pendingStaticAnimParams = _pendingStaticAnimParams;
+
+	snapshot->variables = _variables;
 
 	for (const Common::HashMap<uint, uint32>::Node &timerNode : _timers)
-		timerIDs.push_back(timerNode._key);
+		snapshot->timers[timerNode._key] = timerNode._value - timeBase;
 
-	Common::sort(variableIDs.begin(), variableIDs.end());
-	Common::sort(timerIDs.begin(), timerIDs.end());
+	snapshot->escOn = _escOn;
 
-	stream->writeUint32BE(variableIDs.size());
-	stream->writeUint32BE(timerIDs.size());
+	snapshot->musicTrack = _musicTrack;
 
-	for (uint32 variableKey : variableIDs) {
-		Common::HashMap<uint32, int32>::const_iterator it = _variables.find(variableKey);
-		assert(it != _variables.end());
+	snapshot->loadedAnimation = _loadedAnimation;
+	snapshot->animDisplayingFrame = _animDisplayingFrame;
 
-		stream->writeUint32BE(variableKey);
-		stream->writeSint32BE(it->_value);
-	}
+	for (const Common::SharedPtr<SoundInstance> &soundPtr : _activeSounds) {
+		const SoundInstance &sound = *soundPtr;
+
+		SaveGameSnapshot::Sound saveSound;
+		saveSound.name = sound.name;
+
+		saveSound.id = sound.id;
 
-	for (uint timerKey : timerIDs) {
-		Common::HashMap<uint, uint32>::const_iterator it = _timers.find(timerKey);
-		assert(it != _timers.end());
+		saveSound.volume = sound.volume;
+		saveSound.balance = sound.balance;
 
-		stream->writeUint32BE(timerKey);
-		stream->writeUint32BE(it->_value - timeBase);
+		// Skip ramp
+		if (sound.rampRatePerMSec != 0)
+			saveSound.volume = sound.rampEndVolume;
+
+		saveSound.is3D = sound.is3D;
+		saveSound.isLooping = sound.isLooping;
+		saveSound.isSpeech = sound.isSpeech;
+		saveSound.x = sound.x;
+		saveSound.y = sound.y;
+
+		saveSound.params3D = sound.params3D;
+
+		snapshot->sounds.push_back(saveSound);
 	}
 
-	for (const InventoryItem &item : _inventory)
-		stream->writeUint32BE(item.itemID);
+	snapshot->pendingSoundParams3D = _pendingSoundParams3D;
+
+	snapshot->triggeredOneShots = _triggeredOneShots;
+
+	snapshot->listenerX = _listenerX;
+	snapshot->listenerY = _listenerY;
+	snapshot->listenerAngle = _listenerAngle;
 }
 
-Runtime::LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
-	assert(canLoad());
+void Runtime::restoreSaveGameSnapshot() {
+	uint32 timeBase = g_system->getMillis();
 
-	uint32 saveGameID = stream->readUint32BE();
-	uint32 saveVersion = stream->readUint32BE();
+	for (uint i = 0; i < kNumInventorySlots && i < _saveGame->inventory.size(); i++) {
+		const SaveGameSnapshot::InventoryItem &saveItem = _saveGame->inventory[i];
 
-	if (stream->err() || stream->eos())
-		return kLoadGameOutcomeMissingVersion;
+		_inventory[i].itemID = saveItem.itemID;
+		_inventory[i].highlighted = saveItem.highlighted;
 
-	if (saveGameID != kSaveGameIdentifier)
-		return kLoadGameOutcomeInvalidVersion;
+		if (saveItem.itemID) {
+			Common::String itemFileName = getFileNameForItemGraphic(saveItem.itemID);
+			_inventory[i].graphic = loadGraphic(itemFileName, false);
+		} else {
+			_inventory[i].graphic.reset();
+		}
+	}
 
-	if (saveVersion > kSaveGameCurrentVersion)
-		return kLoadGameOutcomeSaveIsTooNew;
+	_roomNumber = _saveGame->roomNumber;
+	_screenNumber = _saveGame->screenNumber;
+	_direction = _saveGame->direction;
 
-	if (saveVersion < kSaveGameEarliestSupportedVersion)
-		return kLoadGameOutcomeSaveIsTooOld;
+	_pendingStaticAnimParams = _saveGame->pendingStaticAnimParams;
 
-	uint32 timeBase = g_system->getMillis();
+	_variables.clear();
+	_variables = _saveGame->variables;
 
-	uint32 roomNumber = stream->readUint32BE();
-	uint32 screenNumber = stream->readUint32BE();
-	uint32 direction = stream->readUint32BE();
+	_timers.clear();
 
-	uint32 numVars = stream->readUint32BE();
-	uint32 numTimers = stream->readUint32BE();
+	for (const Common::HashMap<uint, uint32>::Node &timerNode : _saveGame->timers)
+		_timers[timerNode._key] = timerNode._value + timeBase;
 
-	if (stream->err() || stream->eos())
-		return kLoadGameOutcomeSaveDataCorrupted;
+	_escOn = _saveGame->escOn;
 
-	Common::HashMap<uint32, int32> vars;
-	Common::HashMap<uint, uint32> timers;
+	changeMusicTrack(_saveGame->musicTrack);
 
-	for (uint32 i = 0; i < numVars; i++) {
-		uint32 varID = stream->readUint32BE();
-		int32 varValue = stream->readSint32BE();
+	// Stop all sounds since the player instances are stored in the sound cache.
+	for (Common::SharedPtr<SoundInstance> &snd : _activeSounds)
+		stopSound(*snd);
 
-		vars[varID] = varValue;
-	}
+	_activeSounds.clear();
+
+	_pendingSoundParams3D = _saveGame->pendingSoundParams3D;
+
+	_triggeredOneShots = _saveGame->triggeredOneShots;
 
-	for (uint32 i = 0; i < numTimers; i++) {
-		uint timerID = stream->readUint32BE();
-		uint32 timerValue = stream->readUint32BE();
+	_listenerX = _saveGame->listenerX;
+	_listenerY = _saveGame->listenerY;
+	_listenerAngle = _saveGame->listenerAngle;
 
-		timers[timerID] = timerValue + timeBase;
+	for (const SaveGameSnapshot::Sound &sound : _saveGame->sounds) {
+		Common::SharedPtr<SoundInstance> si(new SoundInstance());
+
+		si->name = sound.name;
+		si->id = sound.id;
+		si->volume = sound.volume;
+		si->balance = sound.balance;
+		si->is3D = sound.is3D;
+		si->isLooping = sound.isLooping;
+		si->isSpeech = sound.isSpeech;
+		si->x = sound.x;
+		si->y = sound.y;
+		si->params3D = sound.params3D;
+
+		_activeSounds.push_back(si);
+
+		if (sound.isLooping)
+			triggerSound(true, *si, si->volume, si->balance, si->is3D, si->isSpeech);
 	}
 
-	uint inventoryItems[kNumInventorySlots];
+	uint anim = _saveGame->loadedAnimation;
+	uint frame = _saveGame->animDisplayingFrame;
 
-	for (uint i = 0; i < kNumInventorySlots; i++)
-		inventoryItems[i] = stream->readUint32BE();
+	AnimationDef animDef;
+	animDef.animNum = anim;
+	animDef.firstFrame = frame;
+	animDef.lastFrame = frame;
 
-	if (stream->err() || stream->eos())
-		return kLoadGameOutcomeSaveDataCorrupted;
+	changeAnimation(animDef, false);
 
-	if (direction >= kNumDirections)
-		return kLoadGameOutcomeSaveDataCorrupted;
+	_gameState = kGameStateWaitingForAnimation;
+
+	_havePendingScreenChange = true;
+	_forceScreenChange = true;
+}
 
-	// Load succeeded
-	_variables = Common::move(vars);
-	_timers = Common::move(timers);
+void Runtime::saveGame(Common::WriteStream *stream) const {
+	_saveGame->write(stream);
+}
 
-	for (uint i = 0; i < kNumInventorySlots; i++) {
-		_inventory[i].itemID = inventoryItems[i];
-		_inventory[i].highlighted = false;
-		_inventory[i].graphic.reset();
+LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
+	assert(canLoad());
 
-		if (inventoryItems[i] != 0)
-			_inventory[i].graphic = loadGraphic(getFileNameForItemGraphic(inventoryItems[i]), false);
+	Common::SharedPtr<SaveGameSnapshot> snapshot(new SaveGameSnapshot());
+	LoadGameOutcome outcome = snapshot->read(stream);
 
-		drawInventory(i);
-	}
+	if (outcome != kLoadGameOutcomeSucceeded)
+		return outcome;
 
-	_direction = direction;
-	changeToScreen(roomNumber, screenNumber);
-	_havePendingReturnToIdleState = true;
+	_saveGame = snapshot;
+	restoreSaveGameSnapshot();
 
-	return kLoadGameOutcomeSucceeded;
+	return outcome;
 }
 
 #ifdef PEEK_STACK
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 67cfc8ec66d..4c8c908edc4 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -184,6 +184,9 @@ struct SoundParams3D {
 	// Not sure what this does.  It's always shorter than the min range but after many tests, I've been
 	// unable to detect any level changes from altering this parameter.
 	uint unknownRange;
+
+	void write(Common::WriteStream *stream) const;
+	void read(Common::ReadStream *stream);
 };
 
 struct SoundCache {
@@ -220,7 +223,6 @@ struct SoundInstance {
 	bool isSpeech;
 	int32 x;
 	int32 y;
-	int32 z;
 
 	SoundParams3D params3D;
 
@@ -235,6 +237,9 @@ struct TriggeredOneShot {
 
 	uint soundID;
 	uint uniqueSlot;
+
+	void write(Common::WriteStream *stream) const;
+	void read(Common::ReadStream *stream);
 };
 
 struct StaticAnimParams {
@@ -243,6 +248,9 @@ struct StaticAnimParams {
 	uint initialDelay;
 	uint repeatDelay;
 	bool lockInteractions;
+
+	void write(Common::WriteStream *stream) const;
+	void read(Common::ReadStream *stream);
 };
 
 struct StaticAnimation {
@@ -291,19 +299,85 @@ struct Fraction {
 	uint denominator;
 };
 
-class Runtime {
-public:
-	enum LoadGameOutcome {
-		kLoadGameOutcomeSucceeded,
+enum LoadGameOutcome {
+	kLoadGameOutcomeSucceeded,
+
+	kLoadGameOutcomeSaveDataCorrupted,
+
+	kLoadGameOutcomeMissingVersion,
+	kLoadGameOutcomeInvalidVersion,
+	kLoadGameOutcomeSaveIsTooNew,
+	kLoadGameOutcomeSaveIsTooOld,
+};
+
+struct SaveGameSnapshot {
+	SaveGameSnapshot();
+
+	void write(Common::WriteStream *stream) const;
+	LoadGameOutcome read(Common::ReadStream *stream);
+
+	static const uint kSaveGameIdentifier = 0x53566372;
+	static const uint kSaveGameCurrentVersion = 2;
+	static const uint kSaveGameEarliestSupportedVersion = 2;
+
+	struct InventoryItem {
+		InventoryItem();
+
+		uint itemID;
+		bool highlighted;
+
+		void write(Common::WriteStream *stream) const;
+		void read(Common::ReadStream *stream);
+	};
+
+	struct Sound {
+		Sound();
 
-		kLoadGameOutcomeSaveDataCorrupted,
+		Common::String name;
+		uint id;
+		uint volume;
+		int32 balance;
 
-		kLoadGameOutcomeMissingVersion,
-		kLoadGameOutcomeInvalidVersion,
-		kLoadGameOutcomeSaveIsTooNew,
-		kLoadGameOutcomeSaveIsTooOld,
+		bool is3D;
+		bool isLooping;
+		bool isSpeech;
+
+		int32 x;
+		int32 y;
+
+		SoundParams3D params3D;
+
+		void write(Common::WriteStream *stream) const;
+		void read(Common::ReadStream *stream);
 	};
 
+	uint roomNumber;
+	uint screenNumber;
+	uint direction;
+
+	bool escOn;
+	int musicTrack;
+
+	uint loadedAnimation;
+	uint animDisplayingFrame;
+
+	StaticAnimParams pendingStaticAnimParams;
+	SoundParams3D pendingSoundParams3D;
+
+	int32 listenerX;
+	int32 listenerY;
+	int32 listenerAngle;
+
+	Common::Array<InventoryItem> inventory;
+	Common::Array<Sound> sounds;
+	Common::Array<TriggeredOneShot> triggeredOneShots;
+
+	Common::HashMap<uint32, int32> variables;
+	Common::HashMap<uint, uint32> timers;
+};
+
+class Runtime {
+public:
 	Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
 	virtual ~Runtime();
 
@@ -324,6 +398,9 @@ public:
 	bool canSave() const;
 	bool canLoad() const;
 
+	void recordSaveGameSnapshot();
+	void restoreSaveGameSnapshot();
+
 	void saveGame(Common::WriteStream *stream) const;
 	LoadGameOutcome loadGame(Common::ReadStream *stream);
 
@@ -763,6 +840,7 @@ private:
 	Common::SharedPtr<Common::RandomSource> _rng;
 
 	Common::SharedPtr<AudioPlayer> _musicPlayer;
+	int _musicTrack;
 	SfxData _sfxData;
 
 	Common::SharedPtr<Video::AVIDecoder> _animDecoder;
@@ -838,14 +916,12 @@ private:
 	static const int kPanoramaPanningMarginX = 11;
 	static const int kPanoramaPanningMarginY = 11;
 
-	static const uint kSaveGameIdentifier = 0x53566372;
-	static const uint kSaveGameCurrentVersion = 2;
-	static const uint kSaveGameEarliestSupportedVersion = 2;
-
 	static const uint kSoundCacheSize = 16;
 
 	Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
 	uint _soundCacheIndex;
+
+	Common::SharedPtr<SaveGameSnapshot> _saveGame;
 };
 
 } // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index da4df120a48..4964e1ad39d 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -225,32 +225,32 @@ Common::Error VCruiseEngine::saveGameStream(Common::WriteStream *stream, bool is
 }
 
 Common::Error VCruiseEngine::loadGameStream(Common::SeekableReadStream *stream) {
-	Runtime::LoadGameOutcome loadGameOutcome = _runtime->loadGame(stream);
+	LoadGameOutcome loadGameOutcome = _runtime->loadGame(stream);
 
 	switch (loadGameOutcome) {
-	case Runtime::kLoadGameOutcomeSucceeded:
+	case kLoadGameOutcomeSucceeded:
 		return Common::Error(Common::kNoError);
-	case Runtime::kLoadGameOutcomeSaveDataCorrupted: {
+	case kLoadGameOutcomeSaveDataCorrupted: {
 			GUI::MessageDialog dialog(_("Failed to load save, the save data appears to be damaged."));
 			dialog.runModal();
 		}
 		return Common::Error(Common::kReadingFailed);
-	case Runtime::kLoadGameOutcomeMissingVersion: {
+	case kLoadGameOutcomeMissingVersion: {
 			GUI::MessageDialog dialog(_("Failed to read version information from save file."));
 			dialog.runModal();
 		}
 		return Common::Error(Common::kReadingFailed);
-	case Runtime::kLoadGameOutcomeInvalidVersion: {
+	case kLoadGameOutcomeInvalidVersion: {
 			GUI::MessageDialog dialog(_("Failed to load save, the save file doesn't contain valid version information."));
 			dialog.runModal();
 		}
 		return Common::Error(Common::kReadingFailed);
-	case Runtime::kLoadGameOutcomeSaveIsTooNew: {
+	case kLoadGameOutcomeSaveIsTooNew: {
 			GUI::MessageDialog dialog(_("Saved game was created with a newer version of ScummVM. Unable to load."));
 			dialog.runModal();
 		}
 		return Common::Error(Common::kReadingFailed);
-	case Runtime::kLoadGameOutcomeSaveIsTooOld: {
+	case kLoadGameOutcomeSaveIsTooOld: {
 			GUI::MessageDialog dialog(_("Saved game was created with an earlier, incompatible version of ScummVM. Unable to load."));
 			dialog.runModal();
 		}




More information about the Scummvm-git-logs mailing list