[Scummvm-git-logs] scummvm master -> 24429f741792547049431d990bc93c19b3765e2f

elasota noreply at scummvm.org
Tue May 16 00:47:54 UTC 2023


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

Summary:
07d03a6a26 VCRUISE: Implement character swap
b8ee841aad VCRUISE: Add comment explaining why defs are room-linked
5fe99ebc53 VCRUISE: Cut the volume of everything in half to fix integer overflows where one of the speaker volumes would be >255.
388b418b0b VCRUISE: Clear idle animations immediately when executing screen-transitioning ops to prevent them from playing during d
40649e3007 VCRUISE: Add heroGetPos, heroSetPos, BitAnd, BitOr, Mul, Div, and Mod opcodes
9ef7085bff VCRUISE: Fix ItemSelect! op, implement proper animChange op behavior.
8dd26f9daa VCRUISE: Fix up volume behavior, add some more sound ops for Schizm, fix parm op parsing.
24429f7417 VCRUISE: Rework a bunch of things to fix up Schizm's duplicate room behavior.


Commit: 07d03a6a26a17a50c0fa8cf51cc271a5e4dc77c8
    https://github.com/scummvm/scummvm/commit/07d03a6a26a17a50c0fa8cf51cc271a5e4dc77c8
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Implement character swap

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


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 6f0b8bee881..296d69d20e1 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -385,11 +385,19 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
 					c = '/';
 			}
 
+			size_t commentPos = sfxPath.find(';');
+			if (commentPos != Common::String::npos) {
+				sfxPath = sfxPath.substr(0, commentPos);
+				sfxPath.trim();
+			}
+
 			sfxPath = Common::String("Sfx/") + sfxPath;
 
 			Common::File f;
-			if (!f.open(sfxPath))
+			if (!f.open(sfxPath)) {
 				warning("SfxData::load: Could not open sample file '%s'", sfxPath.c_str());
+				continue;
+			}
 
 			int64 size = f.size();
 			if (size <= 0 || size > 0x1fffffffu) {
@@ -2760,6 +2768,28 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
 	}
 }
 
+void Runtime::changeHero() {
+	recordSaveGameSnapshot();
+
+	Common::SharedPtr<SaveGameSwappableState> currentState = _saveGame->states[0];
+	Common::SharedPtr<SaveGameSwappableState> alternateState = _saveGame->states[1];
+
+	if (_swapOutRoom && _swapOutScreen) {
+		// Some scripts may kick the player out to another location on swap back,
+		// such as the elevator in the first area on Hannah's quest
+		currentState->roomNumber = _swapOutRoom;
+		currentState->screenNumber = _swapOutScreen;
+		currentState->direction = _direction;
+	}
+
+	_saveGame->states[0] = alternateState;
+	_saveGame->states[1] = currentState;
+
+	_saveGame->hero ^= 1u;
+
+	restoreSaveGameSnapshot();
+}
+
 void Runtime::triggerPreIdleActions() {
 	debug(1, "Triggering pre-idle actions in room %u screen 0%x facing direction %u", _roomNumber, _screenNumber, _direction);
 
@@ -2869,6 +2899,13 @@ bool Runtime::dischargeIdleMouseMove() {
 		}
 	}
 
+	if (_gameID == GID_SCHIZM && !isOnInteraction) {
+		if (_traySection.rect.contains(_mousePos) && (_traySection.rect.right - _mousePos.x) < 88u) {
+			isOnInteraction = true;
+			interactionID = kHeroChangeInteractionID;
+		}
+	}
+
 	if (_idleIsOnInteraction && (!isOnInteraction || interactionID != _idleInteractionID)) {
 		// Mouse left the previous interaction
 		_idleIsOnInteraction = false;
@@ -2882,12 +2919,17 @@ bool Runtime::dischargeIdleMouseMove() {
 		_idleIsOnInteraction = true;
 		_idleInteractionID = interactionID;
 
-		// New interaction, is there a script?
-		Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
+		if (interactionID == kHeroChangeInteractionID) {
+			changeToCursor(_cursors[16]);
+			_idleHaveClickInteraction = true;
+		} else {
+			// New interaction, is there a script?
+			Common::SharedPtr<Script> script = findScriptForInteraction(interactionID);
 
-		if (script) {
-			activateScript(script, ScriptEnvironmentVars());
-			return true;
+			if (script) {
+				activateScript(script, ScriptEnvironmentVars());
+				return true;
+			}
 		}
 	}
 
@@ -2929,17 +2971,22 @@ bool Runtime::dischargeIdleMouseDown() {
 
 bool Runtime::dischargeIdleClick() {
 	if (_idleIsOnInteraction && _idleHaveClickInteraction) {
-		// Interaction, is there a script?
-		Common::SharedPtr<Script> script = findScriptForInteraction(_idleInteractionID);
+		if (_gameID == GID_SCHIZM && _idleInteractionID == kHeroChangeInteractionID) {
+			changeHero();
+			return true;
+		} else {
+			// Interaction, is there a script?
+			Common::SharedPtr<Script> script = findScriptForInteraction(_idleInteractionID);
 
-		_idleIsOnInteraction = false;	// ?
+			_idleIsOnInteraction = false; // ?
 
-		if (script) {
-			ScriptEnvironmentVars vars;
-			vars.lmb = true;
+			if (script) {
+				ScriptEnvironmentVars vars;
+				vars.lmb = true;
 
-			activateScript(script, vars);
-			return true;
+				activateScript(script, vars);
+				return true;
+			}
 		}
 	}
 
@@ -3787,13 +3834,12 @@ void Runtime::compileSchizmLogicSet(const uint *roomNumbers, uint numRooms) {
 	Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
 
 	for (uint i = 0; i < numRooms; i++) {
-
 		Common::String logicFileName = Common::String::format("Log/Room%02u.log", roomNumbers[i]);
 
 		Common::File logicFile;
 		if (logicFile.open(logicFileName)) {
 			debug(1, "Compiling script %s...", logicFileName.c_str());
-			compileSchizmLogicFile(*scriptSet, logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
+			compileSchizmLogicFile(*scriptSet, roomNumbers[i], logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
 			logicFile.close();
 		}
 	}
@@ -4703,7 +4749,7 @@ void Runtime::recordSaveGameSnapshot() {
 		snapshot->numStates = 1;
 	else if (_gameID == GID_SCHIZM) {
 		snapshot->numStates = 2;
-		snapshot->states[1].reset(new SaveGameSwappableState());
+		snapshot->states[1] = _altState;
 	}
 
 	SaveGameSwappableState *mainState = snapshot->states[0].get();
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index eeb8f623334..f2ba28ecb81 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -786,6 +786,7 @@ private:
 	void resolveSoundByNameOrID(const StackValue &stackValue, bool load, StackInt_t &outSoundID, SoundInstance *&outWave);
 
 	void changeToScreen(uint roomNumber, uint screenNumber);
+	void changeHero();
 	void triggerPreIdleActions();
 	void returnToIdleState();
 	void changeToCursor(const Common::SharedPtr<Graphics::WinCursorGroup> &cursor);
@@ -1240,6 +1241,8 @@ private:
 
 	static const uint kSoundCacheSize = 16;
 
+	static const uint kHeroChangeInteractionID = 0xffffffffu;
+
 	Common::Pair<Common::String, Common::SharedPtr<SoundCache> > _soundCache[kSoundCacheSize];
 	uint _soundCacheIndex;
 
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 6e6f031e128..1d3314c474c 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -149,7 +149,7 @@ struct ScriptNamedInstruction {
 
 class ScriptCompiler {
 public:
-	ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs);
+	ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs);
 
 	void compileScriptSet(ScriptSet *ss);
 
@@ -181,6 +181,7 @@ private:
 	const Common::String _blamePath;
 
 	ScriptDialect _dialect;
+	uint _roomNumber;
 
 	const char *_scrToken;
 	const char *_eroomToken;
@@ -192,9 +193,9 @@ private:
 
 class ScriptCompilerGlobalState : public IScriptCompilerGlobalState {
 public:
-	void define(const Common::String &key, const Common::String &value) override;
+	void define(const Common::String &key, uint roomNumber, int32 value) override;
 
-	const Common::String *getTokenReplacement(const Common::String &str) const override;
+	bool getDefine(const Common::String &str, uint &outRoomNumber, int32 &outValue) const override;
 
 	uint getFunctionIndex(const Common::String &fnName) override;
 	void setFunction(uint fnIndex, const Common::SharedPtr<Script> &fn) override;
@@ -204,14 +205,21 @@ public:
 	Common::SharedPtr<Script> getFunction(uint fnIndex) const override;
 
 private:
-	Common::HashMap<Common::String, Common::String> _defs;
+	struct Def {
+		Def();
+
+		int32 _value;
+		uint _roomNumber;
+	};
+
+	Common::HashMap<Common::String, Def> _defs;
 
 	Common::HashMap<Common::String, uint> _functionNameToIndex;
 	Common::Array<Common::SharedPtr<Script> > _functions;
 };
 
-ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs)
-	: _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath), _dialect(dialect), _gs(gs),
+ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs)
+	: _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath), _dialect(dialect), _roomNumber(roomNumber), _gs(gs),
 	  _scrToken(nullptr), _eroomToken(nullptr) {
 }
 
@@ -418,7 +426,21 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
 			if (!_parser.parseToken(value, state))
 				error("Error compiling script at line %i col %i: Expected value", static_cast<int>(state._lineNum), static_cast<int>(state._col));
 
-			_gs->define(key, value);
+			bool isNegative = false;
+			if (value[0] == '-') {
+				isNegative = true;
+				value = value.substr(1);
+			}
+
+			uint32 number = 0;
+			if (!parseNumber(value, number))
+				error("Error compiling script at line %i col %i: Expected number", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+			int32 signedNumber = static_cast<int32>(number);
+			if (isNegative)
+				signedNumber = -signedNumber;
+
+			_gs->define(key, _roomNumber, signedNumber);
 		} else if (_dialect == kScriptDialectSchizm && token == "~Fun") {
 			Common::String fnName;
 			if (!_parser.parseToken(fnName, state))
@@ -782,17 +804,16 @@ static ScriptNamedInstruction g_schizmNamedInstructions[] = {
 	{"allowedSave", ProtoOp::kProtoOpNoop, ScriptOps::kInvalid},
 };
 
-bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::String &tokenBase) {
-	const Common::String *tokenPtr = &tokenBase;
-
+bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::String &token) {
 	if (_dialect == kScriptDialectSchizm) {
-		const Common::String *ppToken = _gs->getTokenReplacement(tokenBase);
-		if (ppToken)
-			tokenPtr = ppToken;
+		uint roomNumber = 0;
+		int32 value = 0;
+		if (_gs->getDefine(token, roomNumber, value)) {
+			script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, value));
+			return true;
+		}
 	}
 
-	const Common::String &token = *tokenPtr;
-
 	if (_dialect == kScriptDialectSchizm && token.hasPrefix("-")) {
 		uint32 unumber = 0;
 		if (parseNumber(token.substr(1), unumber)) {
@@ -889,11 +910,14 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
 		}
 
 		if (token.size() >= 2 && token.hasSuffix("!")) {
-			if (compileInstructionToken(script, token.substr(0, token.size() - 1))) {
-				script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalStore, 0));
-				return true;
-			} else
+			uint roomNumber = 0;
+			int32 varNumber = 0;
+			if (!_gs->getDefine(token.substr(0, token.size() - 1), roomNumber, varNumber) || varNumber < 0)
 				return false;
+
+			script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kNumber, (roomNumber << 16) + static_cast<uint>(varNumber)));
+			script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalStore, 0));
+			return true;
 		}
 
 		// HACK: Work around bugged variable name in Room02.log
@@ -903,11 +927,14 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
 		}
 
 		if (token.size() >= 2 && token.hasSuffix("@")) {
-			if (compileInstructionToken(script, token.substr(0, token.size() - 1))) {
-				script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalLoad, 0));
-				return true;
-			} else
+			uint roomNumber = 0;
+			int32 varNumber = 0;
+			if (!_gs->getDefine(token.substr(0, token.size() - 1), roomNumber, varNumber) || varNumber < 0)
 				return false;
+
+			script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kNumber, (roomNumber << 16) + static_cast<uint>(varNumber)));
+			script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalLoad, 0));
+			return true;
 		}
 
 		// Does this look like a screen name?
@@ -1252,16 +1279,24 @@ uint ScriptCompiler::indexString(const Common::String &str) {
 	return it->_value;
 }
 
-void ScriptCompilerGlobalState::define(const Common::String &key, const Common::String &value) {
-	_defs.setVal(key, value);
+ScriptCompilerGlobalState::Def::Def() : _value(0), _roomNumber(0) {
 }
 
-const Common::String *ScriptCompilerGlobalState::getTokenReplacement(const Common::String &str) const {
-	Common::HashMap<Common::String, Common::String>::const_iterator it = _defs.find(str);
+void ScriptCompilerGlobalState::define(const Common::String &key, uint roomNumber, int32 value) {
+	Def &def = _defs[key];
+
+	def._roomNumber = roomNumber;
+	def._value = value;
+}
+
+bool ScriptCompilerGlobalState::getDefine(const Common::String &str, uint &outRoomNumber, int32 &outValue) const {
+	Common::HashMap<Common::String, Def>::const_iterator it = _defs.find(str);
 	if (it == _defs.end())
-		return nullptr;
+		return false;
 
-	return &it->_value;
+	outRoomNumber = it->_value._roomNumber;
+	outValue = it->_value._value;
+	return true;
 }
 
 uint ScriptCompilerGlobalState::getFunctionIndex(const Common::String &fnName) {
@@ -1302,11 +1337,11 @@ Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(uint fnIndex) c
 IScriptCompilerGlobalState::~IScriptCompilerGlobalState() {
 }
 
-static void compileLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs) {
+static void compileLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs) {
 	LogicUnscrambleStream unscrambleStream(&stream, streamSize);
 	TextParser parser(&unscrambleStream);
 
-	ScriptCompiler compiler(parser, blamePath, dialect, gs);
+	ScriptCompiler compiler(parser, blamePath, dialect, roomNumber, gs);
 
 	compiler.compileScriptSet(&scriptSet);
 }
@@ -1318,12 +1353,12 @@ Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState()
 Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
 	Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
 
-	compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectReah, nullptr);
+	compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectReah, 0, nullptr);
 	return scriptSet;
 }
 
-void compileSchizmLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs) {
-	compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, gs);
+void compileSchizmLogicFile(ScriptSet &scriptSet, uint roomNumber, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs) {
+	compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, roomNumber, gs);
 }
 
 } // namespace VCruise
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 40cac394e91..b05a0db7fd3 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -268,8 +268,8 @@ struct FunctionDef {
 struct IScriptCompilerGlobalState {
 	virtual ~IScriptCompilerGlobalState();
 
-	virtual void define(const Common::String &key, const Common::String &value) = 0;
-	virtual const Common::String *getTokenReplacement(const Common::String &str) const = 0;
+	virtual void define(const Common::String &key, uint roomNumber, int32 value) = 0;
+	virtual bool getDefine(const Common::String &str, uint &outRoomNumber, int32 &outValue) const = 0;
 
 	virtual uint getFunctionIndex(const Common::String &fnName) = 0;
 	virtual void setFunction(uint fnIndex, const Common::SharedPtr<Script> &fn) = 0;
@@ -281,7 +281,7 @@ struct IScriptCompilerGlobalState {
 
 Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState();
 Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath);
-void compileSchizmLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs);
+void compileSchizmLogicFile(ScriptSet &scriptSet, uint roomNumber, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs);
 
 }
 


Commit: b8ee841aadce59f9437977330e8cd32565d1e480
    https://github.com/scummvm/scummvm/commit/b8ee841aadce59f9437977330e8cd32565d1e480
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Add comment explaining why defs are room-linked

Changed paths:
    engines/vcruise/script.cpp


diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 1d3314c474c..364098ecdbc 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -205,6 +205,12 @@ public:
 	Common::SharedPtr<Script> getFunction(uint fnIndex) const override;
 
 private:
+	// Defs are linked to room numbers to deal with some weird variable sharing behavior.  In Reah,
+	// variables are bound to room, but in Schizm they are clearly not.  fnInitNewGame in Room01.log
+	// initializes a lot of things in various rooms, but some of those IDs also collide with other
+	// things.  For example, dwNawigat2SZ is 13, and is read in Room20, but variable ID 13 is also
+	// used for dwStartSound47 in Room47 to determine if sounds have started, and it is expected
+	// to be initially 0 there.
 	struct Def {
 		Def();
 


Commit: 5fe99ebc532a906e8b61aeca9cb6a45cea820cd0
    https://github.com/scummvm/scummvm/commit/5fe99ebc532a906e8b61aeca9cb6a45cea820cd0
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Cut the volume of everything in half to fix integer overflows where one of the speaker volumes would be >255.

Changed paths:
    engines/vcruise/runtime.cpp


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 296d69d20e1..adf2a769e84 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1029,7 +1029,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
 	_menuInterface.reset(new RuntimeMenuInterface(this));
 
 	for (int32 i = 0; i < 49; i++)
-		_dbToVolume[i] = decibelsToLinear(i - 49, Audio::Mixer::kMaxChannelVolume, Audio::Mixer::kMaxChannelVolume);
+		_dbToVolume[i] = decibelsToLinear(i - 49, Audio::Mixer::kMaxChannelVolume / 2, Audio::Mixer::kMaxChannelVolume / 2);
 }
 
 Runtime::~Runtime() {
@@ -3766,7 +3766,7 @@ uint Runtime::applyVolumeScale(int32 volume) const {
 		else if (volume < 0)
 			return 0;
 
-		return volume * Audio::Mixer::kMaxChannelVolume / 100;
+		return volume * Audio::Mixer::kMaxChannelVolume / 200;
 	}
 }
 
@@ -6419,9 +6419,9 @@ void Runtime::scriptOpSndPlay3D(ScriptArg_t arg) {
 	resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
 
 	SoundParams3D sndParams;
-	sndParams.unknownRange = sndParamArgs[2];
-	sndParams.minRange = sndParamArgs[3];
-	sndParams.maxRange = sndParamArgs[4];
+	sndParams.minRange = sndParamArgs[2];
+	sndParams.maxRange = sndParamArgs[3];
+	sndParams.unknownRange = sndParamArgs[4]; // Doesn't appear to be the same thing as Reah.  Usually 1000, sometimes 2000 or 3000.
 
 	if (cachedSound) {
 		setSound3DParameters(*cachedSound, sndParamArgs[0], sndParamArgs[1], sndParams);


Commit: 388b418b0b6a541f3eca10e8d88283f33728841a
    https://github.com/scummvm/scummvm/commit/388b418b0b6a541f3eca10e8d88283f33728841a
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Clear idle animations immediately when executing screen-transitioning ops to prevent them from playing during delays.  This should still permit them if they're set after executing the op.

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


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index adf2a769e84..92f96db8125 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -2756,6 +2756,8 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
 			_havePanDownFromDirection[i] = false;
 		}
 
+		clearIdleAnimations();
+
 		for (uint i = 0; i < kNumDirections; i++)
 			_haveIdleAnimations[i] = false;
 
@@ -2768,6 +2770,16 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
 	}
 }
 
+void Runtime::clearIdleAnimations() {
+	for (uint i = 0; i < kNumDirections; i++)
+		_haveIdleAnimations[i] = false;
+
+	_havePendingPreIdleActions = true;
+	_haveIdleStaticAnimation = false;
+	_idleCurrentStaticAnimation.clear();
+	_havePendingPlayAmbientSounds = true;
+}
+
 void Runtime::changeHero() {
 	recordSaveGameSnapshot();
 
@@ -2776,7 +2788,8 @@ void Runtime::changeHero() {
 
 	if (_swapOutRoom && _swapOutScreen) {
 		// Some scripts may kick the player out to another location on swap back,
-		// such as the elevator in the first area on Hannah's quest
+		// such as the elevator in the first area on Hannah's quest.  This is done
+		// via the "heroOut" op.
 		currentState->roomNumber = _swapOutRoom;
 		currentState->screenNumber = _swapOutScreen;
 		currentState->direction = _direction;
@@ -2787,6 +2800,8 @@ void Runtime::changeHero() {
 
 	_saveGame->hero ^= 1u;
 
+	changeToCursor(_cursors[kCursorArrow]);
+
 	restoreSaveGameSnapshot();
 }
 
@@ -3186,7 +3201,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
 }
 
 void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate) {
-	debug("changeAnimation: %u -> %u  Initial %u", animDef.firstFrame, animDef.lastFrame, initialFrame);
+	debug("changeAnimation: Anim: %u  Range: %u -> %u  Initial %u", animDef.animNum, animDef.firstFrame, animDef.lastFrame, initialFrame);
 
 	_animPlaylist.reset();
 
@@ -5214,6 +5229,7 @@ void Runtime::scriptOpAnimF(ScriptArg_t arg) {
 	_screenNumber = stackArgs[kAnimDefStackArgs + 0];
 	_direction = stackArgs[kAnimDefStackArgs + 1];
 	_havePendingScreenChange = true;
+	clearIdleAnimations();
 
 	uint cursorID = kCursorArrow;
 	if (_scriptEnv.panInteractionID == kPanUpInteraction)
@@ -5288,6 +5304,8 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
 	_direction = stackArgs[kAnimDefStackArgs + 1];
 	_havePendingScreenChange = true;
 
+	clearIdleAnimations();
+
 	if (_loadedAnimationHasSound)
 		changeToCursor(nullptr);
 	else {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index f2ba28ecb81..e188acc8cf9 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -786,6 +786,7 @@ private:
 	void resolveSoundByNameOrID(const StackValue &stackValue, bool load, StackInt_t &outSoundID, SoundInstance *&outWave);
 
 	void changeToScreen(uint roomNumber, uint screenNumber);
+	void clearIdleAnimations();
 	void changeHero();
 	void triggerPreIdleActions();
 	void returnToIdleState();


Commit: 40649e30076a67e409ccf45b4ca66e3984b889d5
    https://github.com/scummvm/scummvm/commit/40649e30076a67e409ccf45b4ca66e3984b889d5
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Add heroGetPos, heroSetPos, BitAnd, BitOr, Mul, Div, and Mod opcodes

Changed paths:
    engines/vcruise/runtime.cpp


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 92f96db8125..b66837b4d37 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -6578,8 +6578,62 @@ void Runtime::scriptOpHeroOut(ScriptArg_t arg) {
 	_swapOutDirection = stackArgs[2];
 }
 
-OPCODE_STUB(HeroGetPos)
-OPCODE_STUB(HeroSetPos)
+void Runtime::scriptOpHeroGetPos(ScriptArg_t arg) {
+	TAKE_STACK_INT(1);
+
+	bool thisHero = false;
+	switch (stackArgs[0]) {
+	case 0:
+		thisHero = (_hero == 0);
+		break;
+	case 1:
+		thisHero = (_hero == 1);
+		break;
+	case 2:
+		thisHero = false;
+		break;
+	default:
+		error("Unhandled heroGetPos argument %i", static_cast<int>(stackArgs[0]));
+		return;
+	}
+
+	uint roomNumber = thisHero ? _roomNumber : _altState->roomNumber;
+	uint screenNumber = thisHero ? _screenNumber : _altState->screenNumber;
+	uint direction = thisHero ? _direction : _altState->direction;
+
+	uint combined = (roomNumber << 16) | (screenNumber << 8) | direction;
+
+	_scriptStack.push_back(StackValue(static_cast<StackInt_t>(combined)));
+}
+
+void Runtime::scriptOpHeroSetPos(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	bool thisHero = false;
+	switch (stackArgs[0]) {
+	case 0:
+		thisHero = (_hero == 0);
+		break;
+	case 1:
+		thisHero = (_hero == 1);
+		break;
+	case 2:
+		thisHero = false;
+		break;
+	default:
+		error("Unhandled heroGetPos argument %i", static_cast<int>(stackArgs[0]));
+		return;
+	}
+
+	if (!thisHero) {
+		error("heroSetPos for the current hero isn't supported (and Schizm's game scripts shouldn't be doing it).");
+		return;
+	}
+
+	_altState->roomNumber = (stackArgs[1] >> 16) & 0xff;
+	_altState->screenNumber = (stackArgs[1] >> 8) & 0xff;
+	_altState->direction = stackArgs[1] & 0xff;
+}
 
 void Runtime::scriptOpHeroGet(ScriptArg_t arg) {
 	_scriptStack.push_back(StackValue(_hero));
@@ -6587,8 +6641,18 @@ void Runtime::scriptOpHeroGet(ScriptArg_t arg) {
 
 OPCODE_STUB(Garbage)
 OPCODE_STUB(GetRoom)
-OPCODE_STUB(BitAnd)
-OPCODE_STUB(BitOr)
+
+void Runtime::scriptOpBitAnd(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	_scriptStack.push_back(StackValue(stackArgs[0] & stackArgs[1]));
+}
+
+void Runtime::scriptOpBitOr(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	_scriptStack.push_back(StackValue(stackArgs[0] | stackArgs[1]));
+}
 
 void Runtime::scriptOpAngleGet(ScriptArg_t arg) {
 	_scriptStack.push_back(StackValue(_direction));
@@ -6605,9 +6669,35 @@ void Runtime::scriptOpIsCDVersion(ScriptArg_t arg) {
 OPCODE_STUB(Disc)
 OPCODE_STUB(HidePanel)
 OPCODE_STUB(RotateUpdate)
-OPCODE_STUB(Mul)
-OPCODE_STUB(Div)
-OPCODE_STUB(Mod)
+
+void Runtime::scriptOpMul(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	_scriptStack.push_back(StackValue(stackArgs[0] * stackArgs[1]));
+}
+
+void Runtime::scriptOpDiv(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	if (stackArgs[1] == 0) {
+		error("Division by zero");
+		return;
+	}
+
+	_scriptStack.push_back(StackValue(stackArgs[0] / stackArgs[1]));
+}
+
+void Runtime::scriptOpMod(ScriptArg_t arg) {
+	TAKE_STACK_INT(2);
+
+	if (stackArgs[1] == 0) {
+		error("Division by zero");
+		return;
+	}
+
+	_scriptStack.push_back(StackValue(stackArgs[0] % stackArgs[1]));
+}
+
 OPCODE_STUB(CyfraGet)
 OPCODE_STUB(PuzzleInit)
 OPCODE_STUB(PuzzleCanPress)


Commit: 9ef7085bff31e4abae53f87de5af9f7e2d24ba3a
    https://github.com/scummvm/scummvm/commit/9ef7085bff31e4abae53f87de5af9f7e2d24ba3a
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Fix ItemSelect! op, implement proper animChange op behavior.

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


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index b66837b4d37..25a088518ca 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -186,7 +186,8 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
 	return screenDirections[screen][direction].get();
 }
 
-ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), esc(false), exitToMenu(false), panInteractionID(0), fpsOverride(0), lastHighlightedItem(0) {
+ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), esc(false), exitToMenu(false), animChangeSet(false),
+	panInteractionID(0), fpsOverride(0), lastHighlightedItem(0), animChangeFrameOffset(0), animChangeNumFrames(0) {
 }
 
 OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)), keymappedEvent(kKeymappedEventNone), timestamp(0) {
@@ -2155,6 +2156,7 @@ bool Runtime::runScript() {
 			DISPATCH_OP(PuzzleDone);
 			DISPATCH_OP(PuzzleWhoWon);
 			DISPATCH_OP(Fn);
+			DISPATCH_OP(ItemHighlightSetTrue);
 
 		default:
 			error("Unimplemented opcode %i", static_cast<int>(instr.op));
@@ -2166,6 +2168,15 @@ bool Runtime::runScript() {
 
 #undef DISPATCH_OP
 
+bool Runtime::requireAvailableStack(uint n) {
+	if (_scriptStack.size() < n) {
+		error("Script stack underflow");
+		return false;
+	}
+
+	return true;
+}
+
 void Runtime::terminateScript() {
 	_scriptCallStack.clear();
 
@@ -3801,6 +3812,22 @@ AnimationDef Runtime::stackArgsToAnimDef(const StackInt_t *args) const {
 	return def;
 }
 
+void Runtime::adjustUsingAnimChange(AnimationDef &animDef) const {
+	if (_scriptEnv.animChangeSet) {
+		uint origFirstFrame = animDef.firstFrame;
+		uint origLastFrame = animDef.lastFrame;
+
+		uint newFirstFrame = origFirstFrame + _scriptEnv.animChangeFrameOffset;
+		uint newLastFrame = newFirstFrame + _scriptEnv.animChangeNumFrames;
+
+		if (newLastFrame > origLastFrame || newFirstFrame > origLastFrame)
+			warning("animChange ops overran the original animation bounds");
+
+		animDef.firstFrame = newFirstFrame;
+		animDef.lastFrame = newLastFrame;
+	}
+}
+
 void Runtime::pushAnimDef(const AnimationDef &animDef) {
 	_scriptStack.push_back(StackValue(animDef.animNum));
 	_scriptStack.push_back(StackValue(animDef.firstFrame));
@@ -5028,21 +5055,17 @@ LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
 #endif
 
 #define PEEK_STACK(n)                                                                         \
-	if (this->_scriptStack.size() < (n)) {                                                    \
-		error("Script stack underflow");                                                      \
+	if (!requireAvailableStack(n))                                                            \
 		return;                                                                               \
-	}                                                                                         \
 	const StackValue *stackArgs = &this->_scriptStack[this->_scriptStack.size() - (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");                                                  \
+		if (!requireAvailableStack(n))                                                        \
 			return;                                                                           \
-		}                                                                                     \
+		const uint stackSize = _scriptStack.size();                                           \
 		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)];                \
 		for (uint i = 0; i < (n); i++) {                                                      \
 			if (stackArgsPtr[i].type != StackValue::kNumber)                                  \
@@ -5057,11 +5080,9 @@ LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
 #define TAKE_STACK_STR_NAMED(n, arrayName)                                     \
 	Common::String arrayName[n];                                               \
 	do {                                                                       \
-		const uint stackSize = _scriptStack.size();                            \
-		if (stackSize < (n)) {                                                 \
-			error("Script stack underflow");                                   \
+		if (!requireAvailableStack(n))                                         \
 			return;                                                            \
-		}                                                                      \
+		const uint stackSize = _scriptStack.size();                            \
 		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
 		for (uint i = 0; i < (n); i++) {                                       \
 			if (stackArgsPtr[i].type != StackValue::kString)                   \
@@ -5076,11 +5097,9 @@ LoadGameOutcome Runtime::loadGame(Common::ReadStream *stream) {
 #define TAKE_STACK_VAR_NAMED(n, arrayName)                                     \
 	StackValue arrayName[n];                                                   \
 	do {                                                                       \
-		const uint stackSize = _scriptStack.size();                            \
-		if (stackSize < (n)) {                                                 \
-			error("Script stack underflow");                                   \
+		if (!requireAvailableStack(n))                                         \
 			return;                                                            \
-		}                                                                      \
+		const uint stackSize = _scriptStack.size();                            \
 		const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
 		for (uint i = 0; i < (n); i++)                                         \
 			arrayName[i] = Common::move(stackArgsPtr[i]);                      \
@@ -5282,6 +5301,8 @@ void Runtime::scriptOpAnimS(ScriptArg_t arg) {
 
 	AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
 
+	adjustUsingAnimChange(animDef);
+
 	// Static animations start on the last frame
 	changeAnimation(animDef, animDef.lastFrame, false);
 
@@ -5297,6 +5318,9 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
 	TAKE_STACK_INT(kAnimDefStackArgs + 2);
 
 	AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
+
+	adjustUsingAnimChange(animDef);
+
 	changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
 
 	_gameState = kGameStateWaitingForAnimation;
@@ -5452,6 +5476,20 @@ void Runtime::scriptOpItemHighlightSet(ScriptArg_t arg) {
 	}
 }
 
+void Runtime::scriptOpItemHighlightSetTrue(ScriptArg_t arg) {
+	TAKE_STACK_INT(1);
+
+	for (uint slot = 0; slot < kNumInventorySlots; slot++) {
+		InventoryItem &item = _inventory[slot];
+
+		if (item.itemID == static_cast<uint>(stackArgs[0])) {
+			item.highlighted = true;
+			drawInventory(slot);
+			break;
+		}
+	}
+}
+
 void Runtime::scriptOpItemAdd(ScriptArg_t arg) {
 	TAKE_STACK_INT(1);
 
@@ -6487,10 +6525,12 @@ void Runtime::scriptOpAnimVolume(ScriptArg_t arg) {
 void Runtime::scriptOpAnimChange(ScriptArg_t arg) {
 	TAKE_STACK_INT(2);
 
-	(void)stackArgs;
+	if (stackArgs[1] == 0)
+		error("animChange frame count shouldn't be zero");
 
-	// Not sure what this does yet.  It is parameterized in some rooms.
-	warning("animChange opcode isn't implemented yet");
+	_scriptEnv.animChangeSet = true;
+	_scriptEnv.animChangeFrameOffset = stackArgs[0];
+	_scriptEnv.animChangeNumFrames = stackArgs[1] - 1;
 }
 
 void Runtime::scriptOpScreenName(ScriptArg_t arg) {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index e188acc8cf9..911034ac7ed 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -153,10 +153,13 @@ struct ScriptEnvironmentVars {
 	uint panInteractionID;
 	uint fpsOverride;
 	uint lastHighlightedItem;
+	uint animChangeFrameOffset;
+	uint animChangeNumFrames;
 	bool lmb;
 	bool lmbDrag;
 	bool esc;
 	bool exitToMenu;
+	bool animChangeSet;
 };
 
 struct SfxSound {
@@ -756,6 +759,7 @@ private:
 	bool runDelay();
 	bool runHorizontalPan(bool isRight);
 	bool runScript();
+	bool requireAvailableStack(uint n);
 	bool runWaitForAnimation();
 	bool runWaitForFacing();
 	bool runWaitForFacingToAnim();
@@ -824,6 +828,7 @@ private:
 	void stopSubtitles();
 
 	AnimationDef stackArgsToAnimDef(const StackInt_t *args) const;
+	void adjustUsingAnimChange(AnimationDef &animDef) const;
 	void pushAnimDef(const AnimationDef &animDef);
 
 	void activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars);
@@ -1047,6 +1052,7 @@ private:
 	void scriptOpPuzzleDone(ScriptArg_t arg);
 	void scriptOpPuzzleWhoWon(ScriptArg_t arg);
 	void scriptOpFn(ScriptArg_t arg);
+	void scriptOpItemHighlightSetTrue(ScriptArg_t arg);
 
 	Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors;		// Cursors indexed as CURSOR_CUR_##
 	Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort;	// Cursors indexed as CURSOR_#
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 364098ecdbc..9c4389dfc4f 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -761,7 +761,7 @@ static ScriptNamedInstruction g_schizmNamedInstructions[] = {
 	{"save0", ProtoOp::kProtoOpNoop, ScriptOps::kSave0},
 	{"hidePanel", ProtoOp::kProtoOpNoop, ScriptOps::kHidePanel},
 	{"ItemExist@", ProtoOp::kProtoOpScript, ScriptOps::kItemCheck},
-	{"ItemSelect!", ProtoOp::kProtoOpScript, ScriptOps::kItemHighlightSet},
+	{"ItemSelect!", ProtoOp::kProtoOpScript, ScriptOps::kItemHighlightSetTrue},
 	{"ItemPlace@", ProtoOp::kProtoOpScript, ScriptOps::kItemHaveSpace},
 	{"ItemPutInto!", ProtoOp::kProtoOpScript, ScriptOps::kItemAdd},
 	{"ItemRemove!", ProtoOp::kProtoOpScript, ScriptOps::kItemRemove},
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index b05a0db7fd3..c94d10020c0 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -216,6 +216,7 @@ enum ScriptOp {
 	kPuzzleDone,
 	kPuzzleWhoWon,
 	kFn,
+	kItemHighlightSetTrue,
 
 	kNumOps,
 };


Commit: 8dd26f9daacd17753c04bd8ec2275c11e9bf1b99
    https://github.com/scummvm/scummvm/commit/8dd26f9daacd17753c04bd8ec2275c11e9bf1b99
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Fix up volume behavior, add some more sound ops for Schizm, fix parm op parsing.

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


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 25a088518ca..71d67fa3ff2 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1896,11 +1896,11 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
 					VCruise::AudioPlayer &audioPlayer = *playlistEntry.sample->audioPlayer;
 
 					if (playlistEntry.isUpdate) {
-						audioPlayer.setVolumeAndBalance(applyVolumeScale(playlistEntry.volume), playlistEntry.balance);
+						audioPlayer.setVolumeAndBalance(applyVolumeScale(playlistEntry.volume), applyBalanceScale(playlistEntry.balance));
 					} else {
 						audioPlayer.stop();
 						playlistEntry.sample->audioStream->seek(0);
-						audioPlayer.play(applyVolumeScale(playlistEntry.volume), playlistEntry.balance);
+						audioPlayer.play(applyVolumeScale(playlistEntry.volume), applyBalanceScale(playlistEntry.balance));
 					}
 
 					// No break, it's possible for there to be multiple sounds in the same frame
@@ -3648,7 +3648,7 @@ void Runtime::update3DSounds() {
 
 bool Runtime::computeEffectiveVolumeAndBalance(SoundInstance &snd) {
 	uint effectiveVolume = applyVolumeScale(snd.volume);
-	int32 effectiveBalance = snd.balance;
+	int32 effectiveBalance = applyBalanceScale(snd.balance);
 
 	double radians = Common::deg2rad<double>(_listenerAngle);
 	int32 cosAngle = static_cast<int32>(cos(radians) * (1 << 15));
@@ -3796,6 +3796,19 @@ uint Runtime::applyVolumeScale(int32 volume) const {
 	}
 }
 
+int Runtime::applyBalanceScale(int32 balance) const {
+	if (balance < -100)
+		balance = -100;
+	else if (balance > 100)
+		balance = 100;
+
+	// Avoid undefined divide rounding behavior, round toward zero
+	if (balance < 0)
+		return -((-balance) * 127 / 100);
+	else
+		return balance * 127 / 100;
+}
+
 AnimationDef Runtime::stackArgsToAnimDef(const StackInt_t *args) const {
 	AnimationDef def;
 	def.animNum = args[0];
@@ -6362,8 +6375,22 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
 
 
 	Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_scriptSet->strings[arg]);
-	if (it == roomDef->animations.end())
-		error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
+	if (it == roomDef->animations.end()) {
+		bool found = false;
+		for (const Common::SharedPtr<RoomDef> &altRoomDef : _roomDefs) {
+			it = altRoomDef->animations.find(_scriptSet->strings[arg]);
+
+			// Hack to fix PortR_Zwierz_morph being in the wrong room
+			if (it != altRoomDef->animations.end()) {
+				warning("Couldn't resolve animation '%s' in its normal room, but found it in another one, this may cause problems", _scriptSet->strings[arg].c_str());
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
+	}
 
 	pushAnimDef(it->_value);
 }
@@ -6464,7 +6491,17 @@ void Runtime::scriptOpSndPlay(ScriptArg_t arg) {
 		triggerSound(true, *cachedSound, getSilentSoundVolume(), 0, false, false);
 }
 
-OPCODE_STUB(SndPlayEx)
+void Runtime::scriptOpSndPlayEx(ScriptArg_t arg) {
+	TAKE_STACK_INT_NAMED(2, sndParamArgs);
+	TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+	StackInt_t soundID = 0;
+	SoundInstance *cachedSound = nullptr;
+	resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
+
+	if (cachedSound)
+		triggerSound(true, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
+}
 
 void Runtime::scriptOpSndPlay3D(ScriptArg_t arg) {
 	TAKE_STACK_INT_NAMED(5, sndParamArgs);
@@ -6499,7 +6536,16 @@ void Runtime::scriptOpSndWait(ScriptArg_t arg) {
 
 OPCODE_STUB(SndHalt)
 OPCODE_STUB(SndToBack)
-OPCODE_STUB(SndStop)
+
+void Runtime::scriptOpSndStop(ScriptArg_t arg) {
+	TAKE_STACK_INT(1);
+
+	SoundInstance *cachedSound = resolveSoundByID(stackArgs[0]);
+
+	if (cachedSound)
+		stopSound(*cachedSound);
+}
+
 OPCODE_STUB(SndStopAll)
 OPCODE_STUB(SndAddRandom)
 OPCODE_STUB(SndClearRandom)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 911034ac7ed..d311760de10 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -823,6 +823,7 @@ private:
 	int32 getSilentSoundVolume() const;
 	int32 getDefaultSoundVolume() const;
 	uint applyVolumeScale(int32 volume) const;
+	int applyBalanceScale(int32 balance) const;
 
 	void triggerWaveSubtitles(const SoundInstance &sound, const Common::String &id);
 	void stopSubtitles();
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 9c4389dfc4f..ecb7b840675 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -370,8 +370,10 @@ void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
 				uint32 roomNumber = 0;
 
 				if (_parser.parseToken(token, state)) {
-					// Many Schizm rooms use 0xxh as the room number and are empty.  In this case the room is discarded.
-					if (_dialect != kScriptDialectSchizm || token != "0xxh") {
+					// Many Schizm rooms use 0xxh as the room number and are empty.  In this case it's supposed to use a previous valid room.
+					if (_dialect == kScriptDialectSchizm && token == "0xxh") {
+						ss->isAutoGenFromPrevious = true;
+					} else {
 						if (!parseNumber(token, roomNumber))
 							error("Error compiling script at line %i col %i: Expected number but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
 
@@ -773,6 +775,10 @@ static ScriptNamedInstruction g_schizmNamedInstructions[] = {
 	{"puzzleDone", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDone},
 	{"puzzleWhoWon", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleWhoWon},
 	{"fn", ProtoOp::kProtoOpScript, ScriptOps::kFn},
+	{"parm1", ProtoOp::kProtoOpScript, ScriptOps::kParm1},
+	{"parm2", ProtoOp::kProtoOpScript, ScriptOps::kParm2},
+	{"parm3", ProtoOp::kProtoOpScript, ScriptOps::kParm3},
+	{"parmG", ProtoOp::kProtoOpScript, ScriptOps::kParmG},
 
 	{"+", ProtoOp::kProtoOpScript, ScriptOps::kAdd},
 	{"-", ProtoOp::kProtoOpScript, ScriptOps::kSub},
@@ -965,12 +971,15 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
 			return true;
 		}
 
+		// Disabled since Room02 is a cheat room and so not needed.
+#if 0
 		// HACK: Work around broken volume variable names in Room02.  Some of these appear to have "par"
 		// where it should be "vol" but some are garbage.  Figure this out later.
 		if (token.hasPrefix("par")) {
 			script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kGarbage, indexString(token)));
 			return true;
 		}
+#endif
 	}
 
 	return false;
@@ -1340,6 +1349,9 @@ Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(uint fnIndex) c
 	return _functions[fnIndex];
 }
 
+ScriptSet::ScriptSet() : isAutoGenFromPrevious(false) {
+}
+
 IScriptCompilerGlobalState::~IScriptCompilerGlobalState() {
 }
 
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index c94d10020c0..b47498345c2 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -252,11 +252,15 @@ struct RoomScriptSet {
 };
 
 struct ScriptSet {
+	ScriptSet();
+
 	RoomScriptSetMap_t roomScripts;
 
 	Common::Array<Common::SharedPtr<Script> > functions;
 	Common::Array<Common::String> functionNames;
 	Common::Array<Common::String> strings;
+
+	bool isAutoGenFromPrevious;
 };
 
 struct FunctionDef {


Commit: 24429f741792547049431d990bc93c19b3765e2f
    https://github.com/scummvm/scummvm/commit/24429f741792547049431d990bc93c19b3765e2f
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-15T20:47:27-04:00

Commit Message:
VCRUISE: Rework a bunch of things to fix up Schizm's duplicate room behavior.

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


diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 71d67fa3ff2..159e9c957bb 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1209,6 +1209,15 @@ bool Runtime::bootGame(bool newGame) {
 
 		loadScore();
 		debug(1, "Score loaded OK");
+
+		// Duplicate rooms must be identified in advance because they can take effect before the room logic is loaded.
+		// For example, in room 37, when taking the hanging lift across, the room is changed to room 28 and then
+		// animation PortD_Zwierz_morph is used, is an animation mapped to room 25, but we can't know that room 28 is
+		// a duplicate of room 25 unless we check the logic file for rooms 26-28.  Additionally, we can't just scan
+		// downward for missing animations elsewhere because PRZYCUMIE_KRZESELKO is mapped to animations 25 and 26,
+		// but the frame range for 27 and 28 is supposed to use room 25 (the root of the duplication), not 26.
+		loadDuplicateRooms();
+		debug(1, "Duplicated rooms identified OK");
 	} else {
 		StartConfigDef &startConfig = _startConfigs[kStartConfigInitial];
 		startConfig.disc = 1;
@@ -2551,6 +2560,38 @@ void Runtime::loadScore() {
 		warning("Couldn't load music score");
 }
 
+void Runtime::loadDuplicateRooms() {
+	assert(_gameID == GID_SCHIZM);
+
+	Common::ArchiveMemberList logics;
+	SearchMan.listMatchingMembers(logics, "Log/Room##.log", true);
+
+	for (const Common::ArchiveMemberPtr &logic : logics) {
+		Common::String name = logic->getName();
+
+		char d10 = name[4];
+		char d1 = name[5];
+
+		uint roomNumber = (d10 - '0') * 10 + (d1 - '0');
+
+		Common::SharedPtr<Common::SeekableReadStream> stream(logic->createReadStream());
+		if (stream) {
+			if (checkSchizmLogicForDuplicatedRoom(*stream, stream->size())) {
+				if (_roomDuplicationOffsets.size() < roomNumber)
+					_roomDuplicationOffsets.resize(roomNumber + 1);
+				_roomDuplicationOffsets[roomNumber] = 1;
+			}
+		} else {
+			warning("Logic for room %u couldn't be checked for duplication");
+		}
+	}
+
+	for (uint i = 1; i < _roomDuplicationOffsets.size(); i++) {
+		if (_roomDuplicationOffsets[i])
+			_roomDuplicationOffsets[i] += _roomDuplicationOffsets[i - 1];
+	}
+}
+
 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)
@@ -3889,12 +3930,18 @@ void Runtime::compileSchizmLogicSet(const uint *roomNumbers, uint numRooms) {
 	Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
 
 	for (uint i = 0; i < numRooms; i++) {
-		Common::String logicFileName = Common::String::format("Log/Room%02u.log", roomNumbers[i]);
+		uint roomNumber = roomNumbers[i];
+		uint roomFile = roomNumber;
+
+		if (roomNumber < _roomDuplicationOffsets.size())
+			roomFile -= _roomDuplicationOffsets[roomNumber];
+
+		Common::String logicFileName = Common::String::format("Log/Room%02u.log", roomFile);
 
 		Common::File logicFile;
 		if (logicFile.open(logicFileName)) {
 			debug(1, "Compiling script %s...", logicFileName.c_str());
-			compileSchizmLogicFile(*scriptSet, roomNumbers[i], logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
+			compileSchizmLogicFile(*scriptSet, roomNumber, roomFile, logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
 			logicFile.close();
 		}
 	}
@@ -6369,30 +6416,52 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
 	if (_roomNumber >= _roomDefs.size())
 		error("Can't resolve animation for room, room number was invalid");
 
+	Common::String &animName = _scriptSet->strings[arg];
+
+	// In Reah, animations are mapped to rooms.
+	// 
+	// In Schizm this can get very complicated: It supports overlapping room logics which in some cases
+	// have animation ranges mapped to a different animation.
+	//
+	// For example, in Schizm, rooms 25-28 all share one logic file and their corresponding animations are
+	// largely duplicates of each other with different skies.
+	//
+	// It appears that the animation to select is based on the remapped room if the animation can't be
+	// found in its primary room.
+	//
+	// For example, PRZYCUMIE_KRZESELKO is mapped to an animation range in room 25 and another range in
+	// room 26, and there is no mapping for room 28.  In this case, the animation frame range from room 25
+	// is used, but it is remapped to animation 28.
 	Common::SharedPtr<RoomDef> roomDef = _roomDefs[_roomNumber];
-	if (!roomDef)
-		error("Can't resolve animation for room, room number was invalid");
+	if (roomDef) {
+		Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(animName);
+		if (it != roomDef->animations.end()) {
+			pushAnimDef(it->_value);
+			return;
+		}
+	}
 
+	if (_roomNumber < _roomDuplicationOffsets.size() && _roomDuplicationOffsets[_roomNumber] != 0) {
+		int roomToUse = _roomNumber - _roomDuplicationOffsets[_roomNumber];
 
-	Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_scriptSet->strings[arg]);
-	if (it == roomDef->animations.end()) {
-		bool found = false;
-		for (const Common::SharedPtr<RoomDef> &altRoomDef : _roomDefs) {
-			it = altRoomDef->animations.find(_scriptSet->strings[arg]);
+		roomDef = _roomDefs[roomToUse];
 
-			// Hack to fix PortR_Zwierz_morph being in the wrong room
-			if (it != altRoomDef->animations.end()) {
-				warning("Couldn't resolve animation '%s' in its normal room, but found it in another one, this may cause problems", _scriptSet->strings[arg].c_str());
-				found = true;
-				break;
-			}
-		}
+		Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(animName);
+		if (it != roomDef->animations.end()) {
+			AnimationDef animDef = it->_value;
 
-		if (!found)
-			error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
+			if (animDef.animNum == roomToUse)
+				animDef.animNum = _roomNumber;
+			else if (animDef.animNum == -roomToUse)
+				animDef.animNum = -static_cast<int>(_roomNumber);
+
+			pushAnimDef(animDef);
+			return;
+		}
 	}
 
-	pushAnimDef(it->_value);
+
+	error("Can't resolve animation for room, couldn't find animation '%s'", animName.c_str());
 }
 
 void Runtime::scriptOpValueName(ScriptArg_t arg) {
@@ -6726,7 +6795,10 @@ void Runtime::scriptOpHeroGet(ScriptArg_t arg) {
 }
 
 OPCODE_STUB(Garbage)
-OPCODE_STUB(GetRoom)
+
+void Runtime::scriptOpGetRoom(ScriptArg_t arg) {
+	_scriptStack.push_back(StackValue(_roomNumber));
+}
 
 void Runtime::scriptOpBitAnd(ScriptArg_t arg) {
 	TAKE_STACK_INT(2);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index d311760de10..ca095520852 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -783,6 +783,7 @@ private:
 	void findWaves();
 	void loadConfig(const char *cfgPath);
 	void loadScore();
+	void loadDuplicateRooms();
 	Common::SharedPtr<SoundInstance> loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr);
 	SoundCache *loadCache(SoundInstance &sound);
 	void resolveSoundByName(const Common::String &soundName, bool load, StackInt_t &outSoundID, SoundInstance *&outWave);
@@ -1137,6 +1138,7 @@ private:
 	VCruiseGameID _gameID;
 
 	Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
+	Common::Array<uint> _roomDuplicationOffsets;
 	Common::SharedPtr<ScriptSet> _scriptSet;
 
 	Common::Array<CallStackFrame> _scriptCallStack;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index ecb7b840675..339cf219bd0 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -149,7 +149,7 @@ struct ScriptNamedInstruction {
 
 class ScriptCompiler {
 public:
-	ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs);
+	ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint loadAsRoom, uint fileRoom, IScriptCompilerGlobalState *gs);
 
 	void compileScriptSet(ScriptSet *ss);
 
@@ -181,7 +181,8 @@ private:
 	const Common::String _blamePath;
 
 	ScriptDialect _dialect;
-	uint _roomNumber;
+	uint _loadAsRoom;
+	uint _fileRoom;
 
 	const char *_scrToken;
 	const char *_eroomToken;
@@ -224,8 +225,8 @@ private:
 	Common::Array<Common::SharedPtr<Script> > _functions;
 };
 
-ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs)
-	: _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath), _dialect(dialect), _roomNumber(roomNumber), _gs(gs),
+ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, uint loadAsRoom, uint fileRoom, IScriptCompilerGlobalState *gs)
+	: _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath), _dialect(dialect), _loadAsRoom(loadAsRoom), _fileRoom(fileRoom), _gs(gs),
 	  _scrToken(nullptr), _eroomToken(nullptr) {
 }
 
@@ -370,15 +371,13 @@ void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
 				uint32 roomNumber = 0;
 
 				if (_parser.parseToken(token, state)) {
-					// Many Schizm rooms use 0xxh as the room number and are empty.  In this case it's supposed to use a previous valid room.
-					if (_dialect == kScriptDialectSchizm && token == "0xxh") {
-						ss->isAutoGenFromPrevious = true;
-					} else {
-						if (!parseNumber(token, roomNumber))
-							error("Error compiling script at line %i col %i: Expected number but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
-
-						ss->roomScripts[roomNumber] = roomScript;
-					}
+					if (!parseNumber(token, roomNumber))
+						error("Error compiling script at line %i col %i: Expected number but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+
+					if (_dialect == kScriptDialectSchizm && roomNumber == _fileRoom)
+						roomNumber = _loadAsRoom;
+
+					ss->roomScripts[roomNumber] = roomScript;
 				} else {
 					error("Error compiling script at line %i col %i: Expected number", static_cast<int>(state._lineNum), static_cast<int>(state._col));
 				}
@@ -448,7 +447,8 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
 			if (isNegative)
 				signedNumber = -signedNumber;
 
-			_gs->define(key, _roomNumber, signedNumber);
+			// TODO: Figure out if the vars should be scoped in _fileRoom or _loadAsRoom in the case of duplicate rooms
+			_gs->define(key, _fileRoom, signedNumber);
 		} else if (_dialect == kScriptDialectSchizm && token == "~Fun") {
 			Common::String fnName;
 			if (!_parser.parseToken(fnName, state))
@@ -1349,17 +1349,17 @@ Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(uint fnIndex) c
 	return _functions[fnIndex];
 }
 
-ScriptSet::ScriptSet() : isAutoGenFromPrevious(false) {
+ScriptSet::ScriptSet() {
 }
 
 IScriptCompilerGlobalState::~IScriptCompilerGlobalState() {
 }
 
-static void compileLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, ScriptDialect dialect, uint roomNumber, IScriptCompilerGlobalState *gs) {
+static void compileLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, ScriptDialect dialect, uint loadAsRoom, uint fileRoom, IScriptCompilerGlobalState *gs) {
 	LogicUnscrambleStream unscrambleStream(&stream, streamSize);
 	TextParser parser(&unscrambleStream);
 
-	ScriptCompiler compiler(parser, blamePath, dialect, roomNumber, gs);
+	ScriptCompiler compiler(parser, blamePath, dialect, loadAsRoom, fileRoom, gs);
 
 	compiler.compileScriptSet(&scriptSet);
 }
@@ -1371,12 +1371,36 @@ Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState()
 Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
 	Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
 
-	compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectReah, 0, nullptr);
+	compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectReah, 0, 0, nullptr);
 	return scriptSet;
 }
 
-void compileSchizmLogicFile(ScriptSet &scriptSet, uint roomNumber, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs) {
-	compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, roomNumber, gs);
+void compileSchizmLogicFile(ScriptSet &scriptSet, uint loadAsRoom, uint fileRoom, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs) {
+	compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, loadAsRoom, fileRoom, gs);
+}
+
+bool checkSchizmLogicForDuplicatedRoom(Common::ReadStream &stream, uint streamSize) {
+	LogicUnscrambleStream unscrambleStream(&stream, streamSize);
+	TextParser parser(&unscrambleStream);
+
+	TextParserState state;
+	Common::String token;
+
+	const char *expectedTokenSequence[] = {"~Room", "0xxh", "~ERoom"};
+
+	for (const char *tokenExpected : expectedTokenSequence) {
+		if (!parser.parseToken(token, state))
+			return false;
+
+		if (token != tokenExpected)
+			return false;
+	}
+
+	if (parser.parseToken(token, state))
+		return false;
+
+	return true;
 }
 
+
 } // namespace VCruise
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index b47498345c2..e146dc77caf 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -259,8 +259,6 @@ struct ScriptSet {
 	Common::Array<Common::SharedPtr<Script> > functions;
 	Common::Array<Common::String> functionNames;
 	Common::Array<Common::String> strings;
-
-	bool isAutoGenFromPrevious;
 };
 
 struct FunctionDef {
@@ -286,7 +284,8 @@ struct IScriptCompilerGlobalState {
 
 Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState();
 Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath);
-void compileSchizmLogicFile(ScriptSet &scriptSet, uint roomNumber, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs);
+void compileSchizmLogicFile(ScriptSet &scriptSet, uint loadAsRoom, uint fileRoom, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs);
+bool checkSchizmLogicForDuplicatedRoom(Common::ReadStream &stream, uint streamSize);
 
 }
 




More information about the Scummvm-git-logs mailing list