[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