[Scummvm-git-logs] scummvm master -> fbbd1efa66618b17c88f21cf402ec830fba33d4f

Scorpeg noreply at scummvm.org
Wed Jul 22 19:46:43 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:
5f1ab20ed0 PHOENIXVR: Add Russian Pharaoh's Curse detection
fbbd1efa66 PHOENIXVR: Improve initial V2 game support


Commit: 5f1ab20ed0c88eb4255012d90c3c6d52fdddd170
    https://github.com/scummvm/scummvm/commit/5f1ab20ed0c88eb4255012d90c3c6d52fdddd170
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-22T22:41:54+03:00

Commit Message:
PHOENIXVR: Add Russian Pharaoh's Curse detection

Changed paths:
    engines/phoenixvr/detection_tables.h


diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index 61faf8e0879..3f5146e6d6d 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -642,6 +642,17 @@ const ADGameDescription gameDescriptions[] = {
 		ADGF_DROPPLATFORM | ADGF_CD | PHOENIXVR_V2 | ADGF_UNSUPPORTED,
 		GUIO1(GUIO_NONE)
 	},
+	{"pharaoncurse",
+		"Retail CD release - V2 games are not yet supported",
+		AD_ENTRY2s(
+			"script.lst", "559f73dabf49f60afa5983b75ed7a317", 850,
+			"L0P01R01S00.vr", "92ca961d87b8bcdfefefb5e37773a19e", 207721
+		),
+		Common::RU_RUS,
+		Common::kPlatformWindows,
+		ADGF_DROPPLATFORM | ADGF_CD | PHOENIXVR_V2 | ADGF_UNSUPPORTED,
+		GUIO1(GUIO_NONE)
+	},
 
 	AD_TABLE_END_MARKER
 };


Commit: fbbd1efa66618b17c88f21cf402ec830fba33d4f
    https://github.com/scummvm/scummvm/commit/fbbd1efa66618b17c88f21cf402ec830fba33d4f
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-22T22:43:40+03:00

Commit Message:
PHOENIXVR: Improve initial V2 game support

Changed paths:
    engines/phoenixvr/commands_v2.cpp
    engines/phoenixvr/phoenixvr.cpp
    engines/phoenixvr/phoenixvr.h


diff --git a/engines/phoenixvr/commands_v2.cpp b/engines/phoenixvr/commands_v2.cpp
index 4449b5dffd7..5c4e67b5c78 100644
--- a/engines/phoenixvr/commands_v2.cpp
+++ b/engines/phoenixvr/commands_v2.cpp
@@ -70,7 +70,7 @@ struct Goto_Level : public Command {
 struct Enter_Level : public Command {
 	Enter_Level(const Common::Array<Common::String> &args) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("enter level");
+		g_engine->enterLevel();
 	}
 };
 
@@ -282,19 +282,25 @@ struct Sprite_Screen : public Command {
 };
 
 struct Set_Lens : public Command {
-	Common::String index;
+	int index;
 	Common::String name;
-	Common::String unk;
-	Set_Lens(const Common::Array<Common::String> &args) : index(args[0]), name(args[1]), unk(args[2]) {}
+	float size;
+	Set_Lens(const Common::Array<Common::String> &args) : index(valueOf(args[0])), name(args[1]), size(atof(args[2].c_str())) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("set lens %s %s %s", index.c_str(), name.c_str(), unk.c_str());
+		g_engine->setLens(index, name, size);
 	}
 };
 
 struct Set_Lensflare : public Command {
-	Set_Lensflare(const Common::Array<Common::String> &args) {}
+	bool active;
+	float x;
+	float y;
+	Set_Lensflare(const Common::Array<Common::String> &args) : active(args.size() >= 2), x(active ? valueOf(args[0]) * kPi / 180.0f : 0.0f), y(active ? valueOf(args[1]) * kPi / 180.0f : 0.0f) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("set lensflare");
+		if (active)
+			g_engine->setLensflare(x, y);
+		else
+			g_engine->resetLensflare();
 	}
 };
 
@@ -302,14 +308,14 @@ struct Start_Light : public Command {
 	Common::String fx;
 	Start_Light(const Common::Array<Common::String> &args) : fx(args[0]) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("start light %s", fx.c_str());
+		g_engine->startLight(fx);
 	}
 };
 
 struct Stop_Light : public Command {
 	Stop_Light(const Common::Array<Common::String> &args) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("stop light");
+		g_engine->stopLight();
 	}
 };
 
@@ -363,9 +369,10 @@ struct Load_Slot : public Command {
 struct Start_Timer : public Command {
 	float seconds;
 	Common::String warp;
-	Start_Timer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())), warp(args[1].c_str()) {}
+	bool showTimer;
+	Start_Timer(const Common::Array<Common::String> &args) : seconds(atof(args[0].c_str())), warp(args[1].c_str()), showTimer(args.size() < 3 || args[2] == "_SHOW") {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("start timer %g %s", seconds, warp.c_str());
+		g_engine->startTimer(seconds, showTimer, warp);
 	}
 };
 
@@ -380,7 +387,7 @@ struct Limit_View : public Command {
 	Common::String angle1, angle2;
 	Limit_View(const Common::Array<Common::String> &args) : angle1(args[0]), angle2(args[1]) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("limit view %d %d", valueOf(angle1), valueOf(angle2));
+		g_engine->limitView(valueOf(angle1) * kPi / 180.0f, valueOf(angle2) * kPi / 180.0f);
 	}
 };
 
@@ -388,7 +395,7 @@ struct Set_View_Angle : public Command {
 	Common::String angle1, angle2;
 	Set_View_Angle(const Common::Array<Common::String> &args) : angle1(args[0]), angle2(args[1]) {}
 	void exec(ExecutionContext &ctx) const override {
-		warning("set view angle %d %d", valueOf(angle1), valueOf(angle2));
+		g_engine->setAngle(valueOf(angle1) * kPi / 180.0f, valueOf(angle2) * kPi / 180.0f);
 	}
 };
 
@@ -408,6 +415,54 @@ struct Quit_URL : public Command {
 	}
 };
 
+struct UnhandledV2Command : public Command {
+	Common::String name;
+
+	UnhandledV2Command(const Common::String &cmd) : name(cmd) {}
+	void exec(ExecutionContext &ctx) const override {
+		warning("unimplemented v2 command %s", name.c_str());
+	}
+};
+
+static const char *const kUnhandledV2Commands[] = {
+	"AND",
+	"BREAK",
+	"CONTINUE_GAME",
+	"CURSOR_CLOSE",
+	"CURSOR_MODE",
+	"CURSOR_SET",
+	"CURSOR_SET_SPEED",
+	"DELAY_3DSOUND",
+	"DIV",
+	"END",
+	"END_SCRIPT",
+	"EVENT",
+	"GET_MOUSE_BUTTON",
+	"GOTO_REF",
+	"INTERPOLATE_VIEW",
+	"MUL",
+	"NOT",
+	"OR",
+	"PLAY_MUSIC",
+	"QUIT_SCRIPT",
+	"SET_EFFECTS_MODE",
+	"SET_FILTER_KEY",
+	"SET_FILTER_MODE",
+	"SET_MOUSE_BUTTON_MASK",
+	"SET_MUSIC_VOLUME",
+	"SET_VIEW_CLIP",
+	"SPRITES_CLICK_MODE",
+	"SPRITE_CURSOR",
+	"SPRITE_CURSOR_MODE",
+	"SPRITE_SCREEN_MODE",
+	"SPRITE_WARP",
+	"SPRITE_WARP_MODE",
+	"STOP_ANIMBLOC",
+	"STOP_DELAY",
+	"STOP_MUSIC",
+	"UNDERWATER_EFFECT",
+	"ZONES_CLICK_MODE"};
+
 } // namespace
 
 #define COMMAND_LIST(E) \
@@ -454,6 +509,10 @@ struct Quit_URL : public Command {
 
 CommandPtr createV2Command(const Common::String &cmd, const Common::Array<Common::String> &args, int lineno) {
 	COMMAND_LIST(ADD_COMMAND)
+	for (uint i = 0; i < ARRAYSIZE(kUnhandledV2Commands); ++i) {
+		if (cmd.equalsIgnoreCase(kUnhandledV2Commands[i]))
+			return CommandPtr(new UnhandledV2Command(cmd));
+	}
 	error("unhandled command %s at line %d", cmd.c_str(), lineno);
 }
 
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index af5d34f991b..5c4c29b7de1 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -293,12 +293,16 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
 																					 _state(256),
 																					 _loadedCursors(16),
 																					 _sprites(16),
+																					 _lenses(16),
 																					 _fov(kPi2),
 																					 _angleX(0),
 																					 _angleY(-kPi2),
 																					 _mixer(syst->getMixer()) {
 	g_engine = this;
 
+	const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+	SearchMan.addDirectory(gameDataDir, 0, 4);
+
 	if (gameIdMatches("amerzone")) {
 		_levels.push_back({"01VR_PHARE", "Le Phare"});
 		_levels.push_back({"02VR_ILE", "L'Ile"});
@@ -315,7 +319,8 @@ PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDes
 		setNextLevel();
 	} else if (gameIdMatches("pharaoncurse")) {
 		Common::INIFile file;
-		if (!file.loadFromFile("pharaohs.wbm"))
+		Common::ScopedPtr<Common::SeekableReadStream> stream(open("pharaohs.wbm"));
+		if (!stream || !file.loadFromStream(*stream))
 			error("can't open install/pharaohs.wbm");
 		Common::String strNumLevels;
 		if (!file.getKey("LEVELS", "GAME", strNumLevels))
@@ -349,6 +354,8 @@ void PhoenixVREngine::resetState() {
 	_angleY.resetRange();
 	_angleY.set(-kPi2);
 	_imageOverlay.reset();
+	_lensflareActive = false;
+	_lightEffect.clear();
 	_cibleActive = false;
 	_cibleBounds.clear();
 }
@@ -443,7 +450,24 @@ Common::SeekableReadStream *PhoenixVREngine::open(const Common::String &filename
 
 Common::String PhoenixVREngine::getLevelScript(const Level &level) const {
 	auto mainScript = gameIdMatches("amerzone") ? "amerzone" : "script";
-	return Common::String::format("%s\\%s.lst", level.path.c_str(), mainScript);
+	Common::String script = Common::String::format("%s\\%s.lst", level.path.c_str(), mainScript);
+	if (!gameIdMatches("pharaoncurse") || SearchMan.hasFile(Common::Path(script, '\\')))
+		return script;
+
+	if (level.name.equalsIgnoreCase("Menu"))
+		return "script.lst5";
+	if (level.name.equalsIgnoreCase("Level_One"))
+		return "script.lst4";
+	if (level.name.equalsIgnoreCase("Level_Two"))
+		return "script.lst2";
+	if (level.name.equalsIgnoreCase("cd_1"))
+		return "script.lst";
+	if (level.name.equalsIgnoreCase("cd_2"))
+		return "script.lst1";
+	if (level.name.equalsIgnoreCase("Credits"))
+		return "script.lst3";
+
+	return script;
 }
 
 bool PhoenixVREngine::setNextLevel() {
@@ -1269,6 +1293,58 @@ void PhoenixVREngine::spriteScreen(int index, const Common::String &name, int x,
 	sprite.y = y;
 }
 
+void PhoenixVREngine::enterLevel() {
+	enterScript();
+}
+
+void PhoenixVREngine::setLens(int index, const Common::String &name, float size) {
+	debug("set lens %d %s %g", index, name.c_str(), size);
+	if (index < 0 || index >= 16)
+		return;
+
+	_lenses[index].name = name;
+	_lenses[index].scale = size;
+}
+
+void PhoenixVREngine::resetLensflare() {
+	debug("reset lensflare");
+	_lensflareActive = false;
+}
+
+void PhoenixVREngine::setLensflare(float x, float y) {
+	debug("set lensflare %g %g", x, y);
+	_lensflareActive = true;
+	_lensflareX = x;
+	_lensflareY = y;
+}
+
+void PhoenixVREngine::startLight(const Common::String &path) {
+	static const uint kHeaderSize = 0x12;
+	static const uint kPixelCount = 640 * 480;
+
+	Common::ScopedPtr<Common::SeekableReadStream> stream(open(path));
+	if (!stream) {
+		warning("can't open light effect %s", path.c_str());
+		return;
+	}
+	if (stream->size() < kHeaderSize + kPixelCount * 4) {
+		warning("invalid light effect %s", path.c_str());
+		return;
+	}
+
+	_lightEffect.resize(kPixelCount);
+	stream->seek(kHeaderSize);
+	for (uint i = 0; i != kPixelCount; ++i) {
+		uint32 color = stream->readUint32LE();
+		_lightEffect[i] = ((color & 0xf8000000) >> 11) | ((color & 0x00f80000) >> 8) |
+						  ((color & 0x0000fc00) >> 5) | ((color & 0x000000f8) >> 3);
+	}
+}
+
+void PhoenixVREngine::stopLight() {
+	_lightEffect.clear();
+}
+
 void PhoenixVREngine::scheduleTest(int idx) {
 	debug("schedule test %d for execution", idx);
 	_nextTest = idx;
@@ -1285,10 +1361,15 @@ void PhoenixVREngine::executeTest(int idx) {
 }
 
 void PhoenixVREngine::startTimer(float seconds, bool showTimer) {
+	startTimer(seconds, showTimer, Common::String());
+}
+
+void PhoenixVREngine::startTimer(float seconds, bool showTimer, const Common::String &warp) {
 	_timer = seconds;
 	_initialTimer = seconds;
 	_timerFlags = 5;
 	_showTimer = showTimer;
+	_timerWarp = warp;
 }
 
 void PhoenixVREngine::pauseTimer(bool pause, bool deactivate) {
@@ -1307,6 +1388,7 @@ void PhoenixVREngine::pauseTimer(bool pause, bool deactivate) {
 void PhoenixVREngine::killTimer() {
 	_timerFlags = 0;
 	_showTimer = false;
+	_timerWarp.clear();
 }
 
 void PhoenixVREngine::tickTimer(float dt) {
@@ -1322,8 +1404,12 @@ void PhoenixVREngine::tickTimer(float dt) {
 		if (_timerFlags & 4) {
 			if (_timer <= 0) {
 				debug("timer trigger");
+				Common::String timerWarp = _timerWarp;
 				killTimer();
-				scheduleTest(99);
+				if (!timerWarp.empty())
+					goToWarp(timerWarp);
+				else
+					scheduleTest(99);
 			}
 		}
 	}
@@ -1355,6 +1441,101 @@ void PhoenixVREngine::renderTimer() {
 	_screen->simpleBlitFrom(*timerFg, fgSrcRect, fgRect.origin());
 }
 
+void PhoenixVREngine::renderLightEffect() {
+	if (_lightEffect.empty() || _screen->w != 640 || _screen->h != 480)
+		return;
+
+	for (int y = 0; y != 480; ++y) {
+		for (int x = 0; x != 640; ++x) {
+			const uint32 effect = _lightEffect[y * 640 + x];
+			const uint alpha = (effect >> 16) & 0x1f;
+			if (alpha == 0)
+				continue;
+
+			const uint8 srcR = (((effect >> 11) & 0x1f) * 255) / 31;
+			const uint8 srcG = (((effect >> 5) & 0x3f) * 255) / 63;
+			const uint8 srcB = ((effect & 0x1f) * 255) / 31;
+			if (alpha == 31) {
+				_screen->setPixel(x, y, _screen->format.RGBToColor(srcR, srcG, srcB));
+				continue;
+			}
+
+			uint8 dstR, dstG, dstB;
+			_screen->format.colorToRGB(_screen->getPixel(x, y), dstR, dstG, dstB);
+			const uint8 outR = (dstR * (31 - alpha) + srcR * alpha) / 31;
+			const uint8 outG = (dstG * (31 - alpha) + srcG * alpha) / 31;
+			const uint8 outB = (dstB * (31 - alpha) + srcB * alpha) / 31;
+			_screen->setPixel(x, y, _screen->format.RGBToColor(outR, outG, outB));
+		}
+	}
+}
+
+void PhoenixVREngine::renderLensflare() {
+	if (!_lensflareActive)
+		return;
+
+	const float viewX = kPi2 - _angleY.angle();
+	const float viewY = _angleX.angle();
+	const float lensX = _lensflareX;
+	const float lensY = _lensflareY;
+	const float deltaX = lensX - viewX;
+	const float sinLensY = sinf(lensY);
+	const float cosLensY = cosf(lensY);
+	const float sinViewY = sinf(viewY);
+	const float cosViewY = cosf(viewY);
+
+	const float cameraX = sinLensY * sinf(deltaX);
+	const float cameraY = cosViewY * sinLensY * cosf(deltaX) - sinViewY * cosLensY;
+	const float cameraZ = sinViewY * sinLensY * cosf(deltaX) + cosViewY * cosLensY;
+	if (cameraZ <= 0.0f)
+		return;
+
+	const float scaleX = tanf(_fov / 2.0f);
+	const float scaleY = scaleX * _screen->h / _screen->w;
+	if (scaleX == 0.0f || scaleY == 0.0f)
+		return;
+
+	const float sourceX = _screenCenter.x + cameraX / cameraZ * _screenCenter.x / scaleX;
+	const float sourceY = _screenCenter.y - cameraY / cameraZ * _screenCenter.y / scaleY;
+	if (sourceX < 0.0f || sourceX >= _screen->w || sourceY < 0.0f || sourceY >= _screen->h)
+		return;
+
+	for (const Lens &lens : _lenses) {
+		if (lens.name.empty())
+			continue;
+		auto it = _loadedSprites.find(lens.name);
+		if (it == _loadedSprites.end() || !it->_value) {
+			debug("lensflare sprite %s not loaded", lens.name.c_str());
+			continue;
+		}
+
+		const Graphics::ManagedSurface &src = *it->_value;
+		const int dstX = static_cast<int>(sourceX + (_screenCenter.x - sourceX) * lens.scale);
+		const int dstY = static_cast<int>(sourceY + (_screenCenter.y - sourceY) * lens.scale);
+		const uint32 transparentColor = src.hasTransparentColor() ? src.getTransparentColor() : src.format.RGBToColor(0, 0, 0);
+
+		for (int y = 0; y != src.h; ++y) {
+			const int screenY = dstY + y;
+			if (screenY < 0 || screenY >= _screen->h)
+				continue;
+			for (int x = 0; x != src.w; ++x) {
+				const int screenX = dstX + x;
+				if (screenX < 0 || screenX >= _screen->w)
+					continue;
+
+				const uint32 srcColor = src.getPixel(x, y);
+				if (srcColor == transparentColor)
+					continue;
+
+				uint8 srcR, srcG, srcB, dstR, dstG, dstB;
+				src.format.colorToRGB(srcColor, srcR, srcG, srcB);
+				_screen->format.colorToRGB(_screen->getPixel(screenX, screenY), dstR, dstG, dstB);
+				_screen->setPixel(screenX, screenY, _screen->format.RGBToColor(CLIP<int>(dstR + srcR, 0, 255), CLIP<int>(dstG + srcG, 0, 255), CLIP<int>(dstB + srcB, 0, 255)));
+			}
+		}
+	}
+}
+
 void PhoenixVREngine::renderSprites() {
 	if (version() < 2)
 		return;
@@ -1373,6 +1554,8 @@ void PhoenixVREngine::renderSprites() {
 
 void PhoenixVREngine::renderVR(float dt) {
 	_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, dt, _showRegions ? _regSet.get() : nullptr);
+	renderLightEffect();
+	renderLensflare();
 	renderSprites();
 	paintText(_rolloverText);
 	renderArchiveImages();
@@ -1712,9 +1895,6 @@ Common::Error PhoenixVREngine::run() {
 	if (_pixelFormat.isCLUT8())
 		return Common::kUnsupportedColorMode;
 
-	const Common::FSNode gameDataDir(ConfMan.getPath("path"));
-	SearchMan.addDirectory(gameDataDir, 0, 4);
-
 	_arn.reset(ARN::create());
 #ifdef USE_FREETYPE2
 	const Common::Language language = _gameDescription->language;
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 78cb5237f6c..50bfe8cdee3 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -159,6 +159,7 @@ public:
 	void resetLockKey();
 	void lockKey(int idx, const Common::String &warp);
 	void startTimer(float seconds, bool showTimer);
+	void startTimer(float seconds, bool showTimer, const Common::String &warp);
 	void pauseTimer(bool pause, bool deactivate);
 	void killTimer();
 	void playAnimation(const Common::String &name, const Common::String &var, int varValue, float speed);
@@ -173,6 +174,9 @@ public:
 	void setXMax(float max) {
 		_angleY.setRange(-max, max);
 	}
+	void limitView(float min, float max) {
+		_angleY.setRange(kPi2 - max, kPi2 - min);
+	}
 
 	// this is set to large values and effectively useless
 	void setYMax(float min, float max) {
@@ -235,6 +239,12 @@ public:
 
 	void spriteLoad(const Common::String &name, const Common::String &path);
 	void spriteScreen(int index, const Common::String &name, int x, int y);
+	void enterLevel();
+	void setLens(int index, const Common::String &name, float size);
+	void resetLensflare();
+	void setLensflare(float x, float y);
+	void startLight(const Common::String &path);
+	void stopLight();
 
 private:
 	struct ArchiveImage {
@@ -279,6 +289,8 @@ private:
 	void renderTimer();
 	void renderFade(int color);
 	void renderSprites();
+	void renderLensflare();
+	void renderLightEffect();
 	void resetState();
 	const Graphics::Font *getFont(int size, bool bold) const;
 	Common::Path getSubtitlePath(const Common::String &path) const;
@@ -350,6 +362,15 @@ private:
 		int y = 0;
 	};
 	Common::Array<Sprite> _sprites;
+	struct Lens {
+		Common::String name;
+		float scale = 0.0f;
+	};
+	Common::Array<Lens> _lenses;
+	bool _lensflareActive = false;
+	float _lensflareX = 0.0f;
+	float _lensflareY = 0.0f;
+	Common::Array<uint32> _lightEffect;
 
 	Common::Array<Common::Array<Common::String>> _cursors;
 	Common::String _defaultCursor[2];
@@ -369,6 +390,7 @@ private:
 	byte _timerFlags = 0;
 	bool _showTimer = false;
 	float _timer = 0, _initialTimer = 0;
+	Common::String _timerWarp;
 
 	Common::String _contextScript;
 	Common::String _contextLabel;




More information about the Scummvm-git-logs mailing list