[Scummvm-git-logs] scummvm master -> 56e58c4cf95e346cf05d3df881d9ac32ba676510

whoozle noreply at scummvm.org
Mon Mar 9 12:13:17 UTC 2026


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

Summary:
630b1b0547 PHOENIXVR: add initial Amerzone support
56e58c4cf9 PHOENIXVR: update animation variables on first frame if possible


Commit: 630b1b05473d9ce5151daefbd81d3384b0f735eb
    https://github.com/scummvm/scummvm/commit/630b1b05473d9ce5151daefbd81d3384b0f735eb
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-03-09T11:44:26Z

Commit Message:
PHOENIXVR: add initial Amerzone support

- Add GOG release to detection tables
- Make speed arguments for animation blocks optional (default to 25)
- Allow plugin set() and set(), allow script syntax for plugin
- Add script commands/stubs used in Amerzone: SetNord, InterpolateAngle,
  InterpolateAngleZoom, Set_Global_Volume, Set_Global_Pan, Op, Preload, LoadSave, Restart
- Support "levels" used in Amerzone. Add `next_level` command to skip the level completely.
- Some parser quirks like [bool)= or [b\x00\x00ool]

I managed to complete some parts of the game with all obvious problems been fixed, set game status to UNSTABLE

Changed paths:
    engines/phoenixvr/commands.h
    engines/phoenixvr/console.cpp
    engines/phoenixvr/console.h
    engines/phoenixvr/detection_tables.h
    engines/phoenixvr/phoenixvr.cpp
    engines/phoenixvr/phoenixvr.h
    engines/phoenixvr/script.cpp


diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index bd698112039..4ef47538c54 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -77,7 +77,7 @@ struct Play_AnimBloc : public Script::Command {
 	int dstVarValue;
 	float speed; // ticks per second
 
-	Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), dstVar(args[1]), dstVarValue(atoi(args[2].c_str())), speed(atof(args[3].c_str())) {}
+	Play_AnimBloc(const Common::Array<Common::String> &args) : name(args[0]), dstVar(args[1]), dstVarValue(atoi(args[2].c_str())), speed(args.size() >= 4 ? atof(args[3].c_str()) : 25) {}
 	void exec(Script::ExecutionContext &ctx) const override {
 		debug("Play_AnimBloc %s %s %d, %g", name.c_str(), dstVar.c_str(), dstVarValue, speed);
 		g_engine->playAnimation(name, dstVar, dstVarValue, speed);
@@ -102,7 +102,7 @@ struct Play_AnimBloc_Number : public Script::Command {
 
 	Play_AnimBloc_Number(const Common::Array<Common::String> &args) : prefix(args[0]), var(args[1]),
 																	  dstVar(args[2]), dstVarValue(atoi(args[3].c_str())),
-																	  speed(atof(args[4].c_str())) {}
+																	  speed(args.size() >= 5 ? atof(args[4].c_str()) : 25) {}
 	void exec(Script::ExecutionContext &ctx) const override {
 		debug("Play_AnimBloc_Number %s %s %s %d, %g", prefix.c_str(), var.c_str(), dstVar.c_str(), dstVarValue, speed);
 		int value = g_engine->getVariable(var);
@@ -428,6 +428,160 @@ struct LoadSave_Test_Slot : public Script::Command {
 	}
 };
 
+struct Set : public Script::Command {
+	Common::String var;
+	Common::String value;
+	Set(const Common::Array<Common::String> &args) {
+		switch (args.size()) {
+		case 2:
+			var = args[0];
+			value = args[1];
+			break;
+		case 1: {
+			auto pos = args[0].rfind('=');
+			if (pos == Common::String::npos)
+				pos = args[0].size();
+			var = args[0].substr(0, pos);
+			value = args[0].c_str() + pos + 1;
+		} break;
+		default:
+			error("invalid plugin set signature");
+		}
+	}
+	void exec(Script::ExecutionContext &ctx) const override {
+		g_engine->setVariable(var, valueOf(value));
+	}
+};
+
+struct Set_Global_Pan : public Script::Command {
+	Common::String arg;
+	Set_Global_Pan(const Common::Array<Common::String> &args) : arg(args[0]) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		auto value = valueOf(arg);
+		warning("set_global_pan %s -> %d", arg.c_str(), value);
+	}
+};
+
+struct Set_Global_Volume : public Script::Command {
+	Common::String arg;
+	Set_Global_Volume(const Common::Array<Common::String> &args) : arg(args[0]) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		auto value = valueOf(arg);
+		debug("set_global_volume %s -> %d", arg.c_str(), value);
+		g_engine->setGlobalVolume(value);
+	}
+};
+
+struct Op : public Script::Command {
+	Common::String var;
+	Common::String negativeVar;
+	Common::String arg;
+	Op(const Common::Array<Common::String> &args) : var(args[0]), negativeVar(args[1]), arg(args[2]) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		int value = g_engine->getVariable(arg);
+		g_engine->setVariable(var, value);
+		g_engine->setVariable(negativeVar, !value);
+	}
+};
+
+struct Preload : public Script::Command {
+	Common::String arg;
+	Preload(const Common::Array<Common::String> &args) : arg(args[0]) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		debug("preload %s", arg.c_str());
+		if (!arg.empty() && Common::isDigit(arg[0])) {
+			g_engine->loadGameState(atoi(arg.c_str()));
+		} else {
+			auto loaded = g_engine->enterScript();
+			g_engine->setVariable(arg, loaded);
+			if (loaded) {
+				ctx.running = false;
+				g_engine->returnToWarp();
+			}
+		}
+	}
+};
+
+// Old loadsave/save plugins found in Amerzone.
+struct LoadSave : public Script::Command {
+	Common::Array<Common::String> args;
+	LoadSave(const Common::Array<Common::String> &args_) : args(args_) {}
+
+	static bool testSlot(int slot) {
+		auto status = g_engine->testSaveSlot(slot);
+		if (!status)
+			return false;
+
+		static const int faces[] = {4, 3, 5, 1};
+		int face = faces[(slot - 1) / 2];
+		bool odd = (slot - 1) & 1;
+		// taken from necronomicon - misaligned
+		int x = odd ? 275 : 97;
+		int y = 200;
+		g_engine->drawSlot(slot, face, x, y);
+		return true;
+	}
+
+	void exec(Script::ExecutionContext &ctx) const override {
+		uint n = args.size();
+		if (n == 3) {
+			auto slot = atoi(args[0].c_str());
+			auto &var = args[1];
+			auto &negativeVar = args[2];
+			debug("LoadSave %d %s %s", slot, var.c_str(), negativeVar.c_str());
+			int status = 0;
+			// Amerzone script checks those:
+			// 99 -> true, 98 -> false: continue
+			// 99 -> true, 98 -> true: initial main menu
+			// 99 -> false, 98 -> true: new game
+			// false, false -> black screen
+			bool restarted = g_engine->wasRestarted();
+			bool loaded = g_engine->wasLoaded();
+			debug("engine status, loaded: %d, restarted: %d", loaded, restarted);
+			if (slot == 99) {
+				// special save idx - continue game
+				status = !restarted;
+			} else if (slot == 98) {
+				// special save idx - new game started
+				status = !loaded;
+			} else if (slot >= 1 && slot <= 8) {
+				status = testSlot(slot);
+			} else {
+				warning("LoadSave slot %d", slot);
+			}
+			g_engine->setVariable(var, status);
+			g_engine->setVariable(negativeVar, !status);
+		} else if (n == 2) {
+			auto &srcVar = args[0];
+			auto &dstVar = args[1];
+			auto value = g_engine->getVariable(srcVar);
+			g_engine->setVariable(srcVar, 0);
+			g_engine->setVariable(dstVar, value);
+			if (!value) {
+				for (int slot = 1; slot <= 8; ++slot)
+					testSlot(slot);
+			}
+		} else {
+			warning("LoadSave, %u args", n);
+			for (uint i = 0; i < n; ++i)
+				warning("LoadSave %u: %s", i, args[i].c_str());
+		}
+	}
+};
+
+struct Save : public Script::Command {
+	int slot;
+	Save(const Common::Array<Common::String> &args) : slot(atoi(args[0].c_str())) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		debug("Save %d", slot);
+		g_engine->setContextLabel("Amerzone");
+		g_engine->captureContext();
+		auto err = g_engine->saveGameState(slot, {});
+		if (err.getCode() != Common::ErrorCode::kNoError)
+			error("saving state failed %d", slot);
+	}
+};
+
 struct LoadSave_Capture_Context : public Script::Command {
 	LoadSave_Capture_Context(const Common::Array<Common::String> &args) {}
 	void exec(Script::ExecutionContext &ctx) const override {
@@ -496,6 +650,13 @@ struct Reset : public Script::Command {
 	}
 };
 
+struct Restart : public Script::Command {
+	Restart(const Common::Array<Common::String> &args) {}
+	void exec(Script::ExecutionContext &ctx) const override {
+		g_engine->restart();
+	}
+};
+
 struct MemoryRelease : public Script::Command {
 	MemoryRelease(const Common::Array<Common::String> &args) {}
 	void exec(Script::ExecutionContext &ctx) const override {
@@ -578,6 +739,7 @@ struct LoadVariable : public Script::Command {
 struct End : public Script::Command {
 	End(const Common::Array<Common::String> &args) {}
 	void exec(Script::ExecutionContext &ctx) const override {
+		debug("plugin end (quit)");
 		g_engine->quitGame();
 	}
 };
@@ -605,6 +767,11 @@ struct End : public Script::Command {
 	E(SaveCoffre)                    \
 	E(SelectPorteF)                  \
 	E(SelectCoffre)                  \
+	E(Op)                            \
+	E(Set)                           \
+	E(Preload)                       \
+	E(LoadSave)                      \
+	E(Save)                          \
 	E(LoadSave_Capture_Context)      \
 	E(LoadSave_Context_Restored)     \
 	E(LoadSave_Enter_Script)         \
@@ -623,6 +790,7 @@ struct End : public Script::Command {
 	E(Play_AnimBloc_Number)          \
 	E(Play_Movie)                    \
 	E(Reset)                         \
+	E(Restart)                       \
 	E(RemoveObject)                  \
 	E(Rollover)                      \
 	E(RolloverMalette)               \
@@ -630,6 +798,8 @@ struct End : public Script::Command {
 	E(PorteFRollover)                \
 	E(SaveVariable)                  \
 	E(Select)                        \
+	E(Set_Global_Pan)                \
+	E(Set_Global_Volume)             \
 	E(Scroll)                        \
 	E(Stop_AnimBloc)                 \
 	E(DoAction)                      \
@@ -689,11 +859,11 @@ struct IfOr : public Script::Conditional {
 	}
 };
 
-struct Set : public Script::Command {
+struct SetVar : public Script::Command {
 	Common::String name;
 	int value;
 
-	Set(Common::String n, int v) : name(Common::move(n)), value(v) {}
+	SetVar(Common::String n, int v) : name(Common::move(n)), value(v) {}
 	void exec(Script::ExecutionContext &ctx) const override {
 		g_engine->setVariable(name, value);
 	}
@@ -740,7 +910,8 @@ struct SetCursorDefault : public Script::Command {
 	Common::String fname;
 	SetCursorDefault(int i, Common::String f) : idx(i), fname(Common::move(f)) {}
 	void exec(Script::ExecutionContext &ctx) const override {
-		g_engine->setCursorDefault(idx, fname);
+		if (idx >= 0)
+			g_engine->setCursorDefault(idx, fname);
 	}
 };
 
@@ -825,13 +996,23 @@ struct SetAngle : public Script::Command {
 	}
 };
 
+struct SetNord : public Script::Command {
+	float angle;
+	SetNord(float a) : angle(a) {}
+
+	void exec(Script::ExecutionContext &ctx) const override {
+		g_engine->setNord(angle);
+	}
+};
+
 struct InterpolAngle : public Script::Command {
 	float x, y;
 	int unk;
-	InterpolAngle(float x_, float y_, int u) : x(x_), y(y_), unk(u) {}
+	int zoom;
+	InterpolAngle(float x_, float y_, int u, int z) : x(x_), y(y_), unk(u), zoom(z) {}
 
 	void exec(Script::ExecutionContext &ctx) const override {
-		warning("interpolangle %g,%g %d", x, y, unk);
+		warning("interpolangle %g,%g %d %d", x, y, unk, zoom);
 	}
 };
 
@@ -860,6 +1041,16 @@ struct PlaySound : public Script::Command {
 	}
 };
 
+struct PlayRandomSound : public PlaySound {
+	int unk;
+
+	PlayRandomSound(Common::String s, int v, int u, int l) : PlaySound(Common::move(s), v, l), unk(u) {}
+
+	void exec(Script::ExecutionContext &ctx) const override {
+		warning("PlayRandomSound %s %d %d %d", sound.c_str(), volume, unk, loops);
+	}
+};
+
 struct PlayMusique : public PlaySound {
 	PlayMusique(Common::String s, int v) : PlaySound(Common::move(s), v, -1, Audio::Mixer::kMusicSoundType) {}
 };
diff --git a/engines/phoenixvr/console.cpp b/engines/phoenixvr/console.cpp
index f4e3ac4e310..2f7b5e20ed1 100644
--- a/engines/phoenixvr/console.cpp
+++ b/engines/phoenixvr/console.cpp
@@ -28,6 +28,7 @@ Console::Console() : GUI::Debugger() {
 	registerCmd("warp", WRAP_METHOD(Console, cmdWarp));
 	registerCmd("script", WRAP_METHOD(Console, cmdScript));
 	registerCmd("stop_all_sounds", WRAP_METHOD(Console, cmdStopAllSounds));
+	registerCmd("next_level", WRAP_METHOD(Console, cmdNextLevel));
 }
 
 Console::~Console() {
@@ -56,4 +57,10 @@ bool Console::cmdStopAllSounds(int argc, const char **argv) {
 	return false;
 }
 
+bool Console::cmdNextLevel(int argc, const char **argv) {
+	if (g_engine->setNextLevel())
+		g_engine->stopAllSounds();
+	return false;
+}
+
 } // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/console.h b/engines/phoenixvr/console.h
index ea31dc96ecf..2fdd7fd8ce0 100644
--- a/engines/phoenixvr/console.h
+++ b/engines/phoenixvr/console.h
@@ -32,6 +32,7 @@ private:
 	bool cmdWarp(int argc, const char **argv);
 	bool cmdScript(int argc, const char **argv);
 	bool cmdStopAllSounds(int argc, const char **argv);
+	bool cmdNextLevel(int argc, const char **argv);
 
 public:
 	Console();
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index f8ce2e90f0a..036cb03b7a3 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -22,10 +22,13 @@
 #include "advancedDetector.h"
 namespace PhoenixVR {
 
+// clang-format off
+
 const PlainGameDescriptor phoenixvrGames[] = {
 	{"necrono", "Necronomicon: The Dawning of Darkness"},
 	{"lochness", "The Cameron Files: The Secret at Loch Ness"},
 	{"messenger", "The Messenger/Louvre: The Final Curse"},
+	{"amerzone", "Amerzone: The Explorer's Legacy"},
 	{0, 0}
 };
 
@@ -172,7 +175,18 @@ const ADGameDescription gameDescriptions[] = {
 		GUIO1(GUIO_NONE)
 	},
 
+	// GOG release
+	{"amerzone",
+		nullptr,
+		AD_ENTRY1s("amerzone.pak", "bd580dcfe91be9923da608fba72bf128", 314),
+		Common::EN_USA,
+		Common::kPlatformWindows,
+		ADGF_DROPPLATFORM | ADGF_UNSTABLE,
+		GUIO1(GUIO_NONE)
+	},
+
 	AD_TABLE_END_MARKER
 };
+// clang-format on
 
 } // End of namespace PhoenixVR
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index a891d589101..700214d681c 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -75,6 +75,15 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
 	}
 	if (!pixelFormatFound)
 		error("Couldn't find 16/32-bit pixel format");
+
+	if (getGameId() == "amerzone") {
+		_levels.push_back("01VR_PHARE");
+		_levels.push_back("02VR_ILE");
+		_levels.push_back("03VR_PUEBLO");
+		_levels.push_back("04VR_FLEUVE");
+		_levels.push_back("05VR_VILLAGEMARAIS");
+		_levels.push_back("07VRTEMPLE_VOLCAN");
+	}
 }
 
 PhoenixVREngine::~PhoenixVREngine() {
@@ -139,6 +148,17 @@ Common::SeekableReadStream *PhoenixVREngine::open(const Common::String &filename
 	return nullptr;
 }
 
+bool PhoenixVREngine::setNextLevel() {
+	if (_currentLevel < _levels.size()) {
+		auto &level = _levels[_currentLevel++];
+		debug("next level is %s", level.c_str());
+		setNextScript(level + "\\" + getGameId() + ".lst");
+		_loaded = true;
+		return true;
+	} else
+		return false;
+}
+
 void PhoenixVREngine::setNextScript(const Common::String &nextScript) {
 	debug("setNextScript %s", nextScript.c_str());
 	_contextScript = nextScript;
@@ -166,6 +186,8 @@ void PhoenixVREngine::loadNextScript() {
 	_script.reset(new Script(*s));
 	for (auto &var : _script->getVarNames())
 		declareVariable(var);
+	if (getGameId() == "amerzone")
+		declareVariable("oeuf_pose"); // crash in chapter 7
 
 	int numWarps = _script->numWarps();
 	_cursors.clear();
@@ -181,8 +203,10 @@ void PhoenixVREngine::end() {
 	debug("end");
 	stopAllSounds();
 	if (_nextScript.empty() && _nextWarp < 0) {
-		debug("quit game");
-		quitGame();
+		if (!setNextLevel()) {
+			debug("quit game");
+			quitGame();
+		}
 	}
 }
 
@@ -240,6 +264,14 @@ void PhoenixVREngine::wait(float seconds) {
 	}
 }
 
+void PhoenixVREngine::restart() {
+	debug("restart");
+	_restarted = true;
+	_currentLevel = 0;
+	setNextLevel();
+	_loaded = false;
+}
+
 bool PhoenixVREngine::goToWarp(const Common::String &warp, bool savePrev) {
 	debug("gotowarp %s, save prev: %d", warp.c_str(), savePrev);
 	if (_warp && _warp->vrFile == warp) {
@@ -731,7 +763,7 @@ void PhoenixVREngine::tick(float dt) {
 		}
 
 		{
-			Common::ScopedPtr<Common::SeekableReadStream> stream(open(_warp->testFile));
+			Common::ScopedPtr<Common::SeekableReadStream> stream(!_warp->testFile.empty() ? open(_warp->testFile) : nullptr);
 			if (stream)
 				_regSet.reset(new RegionSet(*stream));
 			else
@@ -745,6 +777,7 @@ void PhoenixVREngine::tick(float dt) {
 			test->scope.exec(ctx);
 		else
 			warning("no default script!");
+		_restarted = false;
 	}
 
 	if (_nextTest >= 0) {
@@ -839,7 +872,12 @@ Common::Error PhoenixVREngine::run() {
 			debug("loaded %u textes", _textes.size());
 		}
 	}
-	setNextScript("script.lst");
+
+	// try load level-specific script first (amerzone)
+	if (setNextLevel())
+		_loaded = false; // reset flag or interface.vr will skip menu
+	else
+		setNextScript("script.lst");
 
 	// Set the engine's debugger console
 	setDebugger(new Console());
@@ -1165,8 +1203,22 @@ bool PhoenixVREngine::enterScript() {
 Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot) {
 	auto state = GameState::load(*slot);
 
+	_loaded = true;
 	killTimer();
 	setNextScript(state.script);
+	if (!_levels.empty()) {
+		uint i = 0, n = _levels.size();
+		for (; i != n; ++i) {
+			auto &level = _levels[i];
+			if (state.script.hasPrefixIgnoreCase(level)) {
+				debug("current level is %u", i);
+				_currentLevel = i + 1;
+				break;
+			}
+		}
+		if (i == n)
+			warning("couldn't find current level index for script %s", state.script.c_str());
+	}
 	// keep it alive until loading finishes.
 	auto currentScript = Common::move(_script);
 	assert(!_nextScript.empty());
@@ -1178,6 +1230,7 @@ Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot)
 		Script::ExecutionContext ctx;
 		test->scope.exec(ctx);
 	}
+	_loaded = false;
 
 	return Common::kNoError;
 }
@@ -1260,6 +1313,12 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
 	delete src;
 }
 
+void PhoenixVREngine::setGlobalVolume(int volume) {
+	ConfMan.setInt("music_volume", volume);
+	ConfMan.setInt("sfx_volume", volume);
+	syncSoundSettings();
+}
+
 void PhoenixVREngine::syncSoundSettings() {
 	int musicVolume = ConfMan.getInt("music_volume");
 	int sfxVolume = ConfMan.getInt("sfx_volume");
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 09ad2f51e1a..258b6900944 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -165,6 +165,10 @@ public:
 		_angleY.set(baseX + x);
 	}
 
+	void setNord(float a) {
+		_angleX.set(a);
+	}
+
 	bool testSaveSlot(int idx) const;
 	Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
 	Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
@@ -177,11 +181,18 @@ public:
 	bool enterScript();
 	bool isLoading() const { return !_loadedState.empty(); }
 
+	bool wasRestarted() const { return _restarted; }
+	bool wasLoaded() const { return _loaded; }
+
 	void saveVariables();
 	void loadVariables();
 
 	void rollover(int textId, RolloverType type);
 	void showWaves();
+	void restart();
+	bool setNextLevel();
+
+	void setGlobalVolume(int vol);
 
 private:
 	static Common::String removeDrive(const Common::String &path);
@@ -263,6 +274,12 @@ private:
 
 	Common::ScopedPtr<Graphics::ManagedSurface> _text;
 	Common::Rect _textRect;
+
+	Common::Array<Common::String> _levels;
+	uint _currentLevel = 0;
+
+	bool _restarted = false;
+	bool _loaded = false;
 };
 
 extern PhoenixVREngine *g_engine;
diff --git a/engines/phoenixvr/script.cpp b/engines/phoenixvr/script.cpp
index 2b1332d7af5..8e7ae2b0869 100644
--- a/engines/phoenixvr/script.cpp
+++ b/engines/phoenixvr/script.cpp
@@ -18,6 +18,11 @@ public:
 	}
 
 	void skip() {
+		// comment found in amerzone ",*****"
+		if (_pos == 0 && _line[_pos] == ',') {
+			_pos = _line.size();
+			return;
+		}
 		while (_pos < _line.size() && Common::isSpace(_line[_pos]))
 			++_pos;
 		if (_pos < _line.size() && _line[_pos] == ';')
@@ -93,7 +98,7 @@ public:
 		do {
 			auto ch = next();
 			if (ch < '0' || ch > '9')
-				error("expected digit at %d, line: %s", _pos, _line.c_str());
+				error("expected digit at %d, line: %u, %s", _pos, _lineno, _line.c_str());
 			value = value * 10 + (ch - '0');
 		} while (Common::isDigit(peek()));
 		return negative ? -value : value;
@@ -140,9 +145,12 @@ public:
 	Script::CommandPtr parseCommand() {
 		using CommandPtr = Script::CommandPtr;
 		if (keyword("setcursordefault")) {
-			auto idx = nextInt();
+			auto idx = nextWord();
 			expect(',');
-			return CommandPtr(new SetCursorDefault(idx, nextWord()));
+			bool valid = !idx.empty() && Common::isDigit(idx[0]);
+			// this skips garbage cursor default found in Amerzone
+			// e.g. `setcursordefault cursor1.pcx,cursor1`
+			return CommandPtr(new SetCursorDefault(valid ? atoi(idx.c_str()) : -1, nextWord()));
 		} else if (keyword("lockkey")) {
 			auto idx = nextInt();
 			expect(',');
@@ -167,7 +175,15 @@ public:
 			expect(',');
 			auto a1 = toAngle(nextInt());
 			return CommandPtr(new SetAngle(a0, a1));
-		} else if (keyword("interpolangle")) {
+		} else if (maybe("setnord=")) {
+			auto i0 = nextInt();
+			if (i0 > 4095)
+				i0 -= 8192;
+			auto a0 = toAngle(i0);
+			return CommandPtr(new SetNord(a0));
+		} else if (keyword("interpolangle") || keyword("interpolanglezoom")) {
+			maybe(',');
+			maybe('=');
 			auto i0 = nextInt();
 			if (i0 > 4095)
 				i0 -= 8192;
@@ -176,7 +192,10 @@ public:
 			auto a1 = toAngle(nextInt());
 			expect(',');
 			int unk = nextInt();
-			return CommandPtr(new InterpolAngle(a0, a1, unk));
+			int zoom = 0;
+			if (maybe(','))
+				zoom = nextInt();
+			return CommandPtr(new InterpolAngle(a0, a1, unk, zoom));
 		} else if (maybe("anglexmax=")) {
 			return CommandPtr(new AngleXMax(toAngle(nextInt())));
 		} else if (maybe("angleymax=")) {
@@ -192,8 +211,9 @@ public:
 			auto arg0 = nextInt();
 			expect(',');
 			auto arg1 = nextInt();
-			expect(',');
-			auto arg2 = nextInt();
+			int arg2 = 0;
+			if (maybe(','))
+				arg2 = nextInt();
 			return CommandPtr(new PlaySound3D(Common::move(sound), arg0, toAngle(arg1), arg2));
 		} else if (keyword("playmusique")) {
 			auto sound = nextWord();
@@ -202,19 +222,30 @@ public:
 				vol = nextInt();
 			return CommandPtr(new PlayMusique(Common::move(sound), vol));
 		} else if (keyword("playsound")) {
+			auto sound = nextWord();
+			expect(',');
+			auto arg0 = nextInt();
+			int arg1 = 0;
+			if (maybe(','))
+				arg1 = nextInt();
+			return CommandPtr(new PlaySound(Common::move(sound), arg0, arg1));
+		} else if (keyword("playrndsound")) {
 			auto sound = nextWord();
 			expect(',');
 			auto arg0 = nextInt();
 			expect(',');
 			auto arg1 = nextInt();
-			return CommandPtr(new PlaySound(Common::move(sound), arg0, arg1));
+			int arg2 = 0;
+			if (maybe(','))
+				arg2 = nextInt();
+			return CommandPtr(new PlayRandomSound(Common::move(sound), arg0, arg1, arg2));
 		} else if (keyword("stopsound3d")) {
 			return CommandPtr(new StopSound3D(nextWord()));
 		} else if (keyword("stopsound") || keyword("stopmusique")) {
 			return CommandPtr(new StopSound(nextWord()));
 		} else if (keyword("setcursor")) {
 			auto image = nextWord();
-			expect(',');
+			maybe(',');
 			auto warp = nextWord();
 			int idx = 0;
 			if (maybe(','))
@@ -227,9 +258,18 @@ public:
 			return CommandPtr(new HideCursor(Common::move(warp), idx));
 		} else if (keyword("set")) {
 			auto var = nextWord();
-			expect('=');
-			auto value = nextInt();
-			return CommandPtr(new Set(Common::move(var), value));
+			if (maybe(',')) {
+				// this is typo in amerzone, this meant to be setCursor
+				auto warp = nextWord();
+				int idx = 0;
+				if (maybe(','))
+					idx = nextInt();
+				return CommandPtr(new SetCursor(Common::move(var), Common::move(warp), idx));
+			}
+			int value = 0;
+			if (maybe('='))
+				value = nextInt();
+			return CommandPtr(new SetVar(Common::move(var), value));
 		} else if (keyword("not")) {
 			auto var = nextWord();
 			return CommandPtr(new Not(Common::move(var)));
@@ -283,13 +323,14 @@ void Script::parseLine(const Common::String &line, uint lineno) {
 		return;
 
 	if (p.maybe('[')) {
-		if (p.maybe("bool]=") || p.maybe("bool)=")) {
+		if (p.maybe("bool]=") || p.maybe("bool)=") || p.maybe("b\x00\x00ool]=")) {
 			_vars.push_back(p.nextWord());
 		} else if (p.maybe("warp]=")) {
 			auto vr = p.nextWord();
-			p.expect(',');
-			auto test = p.nextWord();
-			_currentWarp.reset(new Warp{vr, test, {}});
+			Common::String test;
+			if (p.maybe(','))
+				test = p.nextWord();
+			_currentWarp.reset(new Warp{vr, Common::move(test), {}});
 			_warpsIndex[vr] = _warps.size();
 			_warps.push_back(_currentWarp);
 			_warpNames.push_back(vr);


Commit: 56e58c4cf95e346cf05d3df881d9ac32ba676510
    https://github.com/scummvm/scummvm/commit/56e58c4cf95e346cf05d3df881d9ac32ba676510
Author: Vladimir Menshakov (vladimir.menshakov at gmail.com)
Date: 2026-03-09T12:11:57Z

Commit Message:
PHOENIXVR: update animation variables on first frame if possible

Fixes occasional freezes when clicking buttons on plane's dashboard
in Amerzone Chapter 2.

If single frame animations reuse the same variable with multiframe one,
updates can clash and make until(var, 1) freeze later.

We always render first frame from play_anim_block, because it often used
as a static "patches", so update single frame animations instantly, not on the next tick.

Changed paths:
    engines/phoenixvr/vr.cpp


diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 86dcadaf5fd..1b19b75e580 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -396,10 +396,7 @@ void VR::stopAnimation(const Common::String &name) {
 
 void VR::Animation::renderNextFrame(Graphics::Surface &pic) {
 	assert(active);
-	if (frameIndex >= frames.size()) {
-		active = false;
-		g_engine->setVariable(variable, variableValue);
-	} else {
+	if (frameIndex < frames.size()) {
 		auto &frame = frames[frameIndex++];
 		frame.render(pic);
 		if (frame.restartAtFrame >= 0) {
@@ -407,6 +404,10 @@ void VR::Animation::renderNextFrame(Graphics::Surface &pic) {
 			t = 1;
 		}
 	}
+	if (frameIndex >= frames.size()) {
+		active = false;
+		g_engine->setVariable(variable, variableValue);
+	}
 }
 
 void VR::Animation::render(Graphics::Surface &pic, float dt) {




More information about the Scummvm-git-logs mailing list