[Scummvm-git-logs] scummvm master -> 3d68656634218d213a8aa3912573c85cc2d55850
whoozle
noreply at scummvm.org
Thu Jun 11 22:10:35 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
c77e04e211 PHOENIXVR: Improve text font handling
b06f7828e1 VIDEO: Allow Phoenix 4XM v1 videos
855a48e3a6 PHOENIXVR: Add more game variants
a16464659b PHOENIXVR: Improve save room rendering
e07b3297ab PHOENIXVR: Fix Amerzone save room level restore
3d68656634 PHOENIXVR: Add more game variants
Commit: c77e04e2118a4b82ff732a9d8e1b2e560c82e49a
https://github.com/scummvm/scummvm/commit/c77e04e2118a4b82ff732a9d8e1b2e560c82e49a
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
PHOENIXVR: Improve text font handling
Changed paths:
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 265e9a580be..fe7163b293d 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -29,6 +29,7 @@
#include "common/memstream.h"
#include "common/savefile.h"
#include "common/scummsys.h"
+#include "common/str-enc.h"
#include "common/system.h"
#include "engines/util.h"
#include "graphics/font.h"
@@ -54,6 +55,15 @@ namespace PhoenixVR {
PhoenixVREngine *g_engine;
+static Common::CodePage getTextCodePage(Common::Language language) {
+ switch (language) {
+ case Common::RU_RUS:
+ return Common::kWindows1251;
+ default:
+ return Common::kWindows1252;
+ }
+}
+
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_frameLimiter(g_system, kFPSLimit),
_gameDescription(gameDesc),
@@ -758,12 +768,16 @@ void PhoenixVREngine::loadVariables() {
const Graphics::Font *PhoenixVREngine::getFont(int size, bool bold) const {
#ifdef USE_FREETYPE2
- if (size < 14)
- return _font12.get();
- else if (size < 18)
- return _font14.get();
- else
- return _font18.get();
+ const int fontMaxSizes[] = {10, 12, 14, 16, 18, INT_MAX};
+
+ for (uint i = 0; i < ARRAYSIZE(fontMaxSizes); ++i) {
+ if (size < fontMaxSizes[i]) {
+ const Graphics::Font *font = bold ? _boldFonts[i].get() : nullptr;
+ return font ? font : _regularFonts[i].get();
+ }
+ }
+
+ return _regularFonts[ARRAYSIZE(fontMaxSizes) - 1].get();
#else
return FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
#endif
@@ -822,9 +836,9 @@ void PhoenixVREngine::rollover(int textId, RolloverType type) {
return;
}
auto &text = _textes.getVal(textId);
- debug("rollover %s, %s font size: %d, bold: %d, color: %02x", dstRect.toString().c_str(), text.c_str(), size, bold, color);
+ debug("rollover %s, %s font size: %d, bold: %d, color: %02x", dstRect.toString().c_str(), text.encode(Common::kUtf8).c_str(), size, bold, color);
- Common::Array<Common::String> lines;
+ Common::Array<Common::U32String> lines;
font->wordWrapText(text, dstRect.width(), lines, Graphics::kWordWrapDefault);
auto fontH = font->getFontHeight();
@@ -983,10 +997,13 @@ Common::Error PhoenixVREngine::run() {
_arn.reset(ARN::create());
#ifdef USE_FREETYPE2
- static const Common::String family("NotoSerif-Bold.ttf");
- _font12.reset(Graphics::loadTTFFontFromArchive(family, 12));
- _font14.reset(Graphics::loadTTFFontFromArchive(family, 14));
- _font18.reset(Graphics::loadTTFFontFromArchive(family, 18));
+ static const Common::String regular("NotoSans-Regular.ttf");
+ static const Common::String bold("NotoSans-Bold.ttf");
+ const int fontSizes[] = {8, 10, 12, 14, 16, 18};
+ for (uint i = 0; i < ARRAYSIZE(fontSizes); ++i) {
+ _regularFonts[i].reset(Graphics::loadTTFFontFromArchive(regular, fontSizes[i]));
+ _boldFonts[i].reset(Graphics::loadTTFFontFromArchive(bold, fontSizes[i]));
+ }
#endif
setCursorDefault(0, "Cursor1.pcx");
@@ -1010,6 +1027,7 @@ Common::Error PhoenixVREngine::run() {
{
Common::File textes;
if (textes.open(Common::Path("textes.txt"))) {
+ Common::CodePage textCodePage = getTextCodePage(_gameDescription->language);
while (!textes.eos()) {
auto text = textes.readLine();
if (text.empty() || text[0] != '*')
@@ -1022,7 +1040,7 @@ Common::Error PhoenixVREngine::run() {
++pos;
while (pos < text.size() && Common::isSpace(text[pos]))
++pos;
- _textes.setVal(textId, text.substr(pos));
+ _textes.setVal(textId, Common::convertToU32String(text.c_str() + pos, textCodePage));
}
debug("loaded %u textes", _textes.size());
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 322c6be74ec..07f411c38c9 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -272,11 +272,11 @@ private:
Common::Array<byte> _capturedState;
Common::Array<byte> _loadedState;
- Common::HashMap<int, Common::String> _textes;
+ Common::HashMap<int, Common::U32String> _textes;
- Common::ScopedPtr<Graphics::Font> _font12;
- Common::ScopedPtr<Graphics::Font> _font14;
- Common::ScopedPtr<Graphics::Font> _font18;
+ static const int kFontSizeCount = 6;
+ Common::ScopedPtr<Graphics::Font> _regularFonts[kFontSizeCount];
+ Common::ScopedPtr<Graphics::Font> _boldFonts[kFontSizeCount];
Common::ScopedPtr<Graphics::ManagedSurface> _text;
Common::Rect _textRect;
Commit: b06f7828e18dfc6185d7faed116531418a491df9
https://github.com/scummvm/scummvm/commit/b06f7828e18dfc6185d7faed116531418a491df9
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
VIDEO: Allow Phoenix 4XM v1 videos
Changed paths:
video/4xm_decoder.cpp
diff --git a/video/4xm_decoder.cpp b/video/4xm_decoder.cpp
index 5c3cc2d0695..36d2e9ed977 100644
--- a/video/4xm_decoder.cpp
+++ b/video/4xm_decoder.cpp
@@ -108,8 +108,6 @@ class FourXMDecoder::FourXMVideoTrack : public FixedRateVideoTrack {
public:
FourXMVideoTrack(FourXMDecoder *dec, const Common::Rational &frameRate, uint w, uint h, uint16 version) : _dec(dec), _frameRate(frameRate), _w(w), _h(h), _version(version) {
- if (_version <= 1)
- error("versions 0 and 1 are not supported");
_blockType[0].reset(new HuffmanType(HuffmanType::fromFrequencies({16, 8, 4, 2, 1, 1})));
_blockType[1].reset(new HuffmanType(HuffmanType::fromFrequencies({8, 0, 4, 2, 1, 1})));
_blockType[2].reset(new HuffmanType(HuffmanType::fromFrequencies({8, 4, 0, 2, 1, 1})));
@@ -418,7 +416,7 @@ void FourXMDecoder::FourXMVideoTrack::decode_pfrm_block(uint16 *dst, const uint1
}
return;
}
- if (code == 3 && _version >= 2)
+ if (code == 3)
return;
if (code == 0) {
Commit: 855a48e3a66c4cb624a678e7a2788cf149944069
https://github.com/scummvm/scummvm/commit/855a48e3a66c4cb624a678e7a2788cf149944069
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
PHOENIXVR: Add more game variants
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index 75e68d363ef..efe84a36f11 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -75,6 +75,16 @@ const ADGameDescription gameDescriptions[] = {
GUIO1(GUIO_NONE)
},
+ {"necrono",
+ nullptr,
+ AD_ENTRY2s("script.pak", "da42a18dd02fc01f116228d5c219b2fd", 215,
+ "textes.txt", "ab8efb7f5e92d2b76863c181bf2dbd10", 4959),
+ Common::RU_RUS,
+ Common::kPlatformWindows,
+ ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NONE)
+ },
+
{"necrono",
nullptr,
AD_ENTRY2s("script.pak", "86294b9c445c3e06e24269c84036a207", 223,
@@ -125,6 +135,16 @@ const ADGameDescription gameDescriptions[] = {
GUIO1(GUIO_NONE)}
,
+ {"lochness",
+ nullptr,
+ AD_ENTRY2s("script.pak", "a7ee3aae653658f93bba7f237bcf06f3", 1904,
+ "textes.txt", "d1546d04243ee63f9ff6c5fc551082e1", 1763),
+ Common::RU_RUS,
+ Common::kPlatformWindows,
+ ADGF_DROPPLATFORM,
+ GUIO1(GUIO_NONE)
+ },
+
{"lochness",
nullptr,
AD_ENTRY2s("script.pak", "a7ee3aae653658f93bba7f237bcf06f3", 1904,
Commit: a16464659b280ccb85b0f754efb5b6ce1c6d77d6
https://github.com/scummvm/scummvm/commit/a16464659b280ccb85b0f754efb5b6ce1c6d77d6
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
PHOENIXVR: Improve save room rendering
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/game_state.cpp
engines/phoenixvr/metaengine.cpp
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 4c79268aca6..4750a3538e2 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -512,13 +512,7 @@ struct LoadSave : public Script::Command {
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);
+ g_engine->drawSaveCard(slot);
return true;
}
diff --git a/engines/phoenixvr/game_state.cpp b/engines/phoenixvr/game_state.cpp
index b24e1a8abd4..89e2e45e9d5 100644
--- a/engines/phoenixvr/game_state.cpp
+++ b/engines/phoenixvr/game_state.cpp
@@ -3,19 +3,31 @@
#include "graphics/surface.h"
namespace PhoenixVR {
+
+static Common::String readSaveText(Common::SeekableReadStream &stream) {
+ const uint32 size = stream.readUint32LE();
+ Common::String result;
+
+ for (uint32 i = 0; i < size; ++i) {
+ const byte c = stream.readByte();
+ if (c == 0) {
+ result += '\n';
+ } else {
+ result += static_cast<char>(c);
+ }
+ }
+
+ return result;
+}
+
GameState GameState::load(Common::SeekableReadStream &stream) {
GameState state;
- auto readString = [&]() {
- auto size = stream.readUint32LE();
- return stream.readString(0, size);
- };
-
- state.script = readString();
+ state.script = readSaveText(stream);
debug("save.script: %s", state.script.c_str());
- state.game = readString();
+ state.game = readSaveText(stream);
debug("save.game: %s", state.game.c_str());
- state.info = readString();
+ state.info = readSaveText(stream);
debug("save.datetime: %s", state.info.c_str());
uint dibHeaderSize = stream.readUint32LE();
stream.seek(-4, SEEK_CUR);
diff --git a/engines/phoenixvr/metaengine.cpp b/engines/phoenixvr/metaengine.cpp
index ae4dd2e88aa..19f1d24031a 100644
--- a/engines/phoenixvr/metaengine.cpp
+++ b/engines/phoenixvr/metaengine.cpp
@@ -33,6 +33,15 @@
namespace PhoenixVR {
+static Common::String flattenSaveText(const Common::String &text) {
+ Common::String result = text;
+ for (uint i = 0; i < result.size(); ++i) {
+ if (result[i] == '\n')
+ result.setChar(' ', i);
+ }
+ return result;
+}
+
static const ADExtraGuiOptionsMap optionsList[] = {
{GAMEOPTION_ORIGINAL_SAVELOAD,
{_s("Use original save/load screens"),
@@ -150,7 +159,7 @@ SaveStateDescriptor PhoenixVRMetaEngine::querySaveMetaInfos(const char *target,
SaveStateDescriptor desc;
desc.setSaveSlot(slotIdx);
desc.setDeletableFlag(true);
- desc.setDescription(state.game + " " + state.info);
+ desc.setDescription(PhoenixVR::flattenSaveText(state.game) + " " + PhoenixVR::flattenSaveText(state.info));
Graphics::PixelFormat rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0);
desc.setThumbnail(state.getThumbnail(rgb565, 160));
return desc;
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index fe7163b293d..ab34e347fb7 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -64,12 +64,237 @@ static Common::CodePage getTextCodePage(Common::Language language) {
}
}
+static bool isAmerzoneGame(const ADGameDescription *gameDesc) {
+ return !strcmp(gameDesc->gameId, "amerzone");
+}
+
+static Common::String getAmerzoneLevelLabel(const Common::String &script) {
+ static const struct {
+ const char *prefix;
+ const char *label;
+ } levels[] = {
+ {"01VR_PHARE", "Le Phare"},
+ {"02VR_ILE", "L'Ile"},
+ {"03VR_PUEBLO", "Le Pueblo"},
+ {"04VR_FLEUVE", "Le Fleuve"},
+ {"05VR_VILLAGEMARAIS", "Le Village"},
+ {"07VRTEMPLE_VOLCAN", "Le Temple"}
+ };
+
+ for (const auto &level : levels) {
+ if (script.hasPrefixIgnoreCase(level.prefix))
+ return level.label;
+ }
+
+ return "Amerzone";
+}
+
+static const char *mfull[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *wday[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+};
+
+static Common::String makeSaveText(const Common::String &firstLine, const Common::String &secondLine) {
+ Common::String result = firstLine;
+ result += '\0';
+ result += secondLine;
+ return result;
+}
+
+static Common::String makeSaveText(const Common::String &firstLine, const Common::String &secondLine, const Common::String &thirdLine) {
+ Common::String result = makeSaveText(firstLine, secondLine);
+ result += '\0';
+ result += thirdLine;
+ return result;
+}
+
+static Common::String formatSaveInfo(const TimeDate &td, bool longDate, const Common::String &place = Common::String()) {
+ if (longDate) {
+ return makeSaveText(
+ Common::String::format("%s, %s %d, %04d", wday[td.tm_wday], mfull[td.tm_mon], td.tm_mday, td.tm_year + 1900),
+ Common::String::format("%02d:%02d:%02d %s", td.tm_hour, td.tm_min, td.tm_sec, td.tm_hour < 12 ? "AM" : "PM"),
+ place);
+ }
+
+ return makeSaveText(
+ Common::String::format("%s %02d %02d %04d", wday[td.tm_wday], td.tm_mday, td.tm_mon + 1, td.tm_year + 1900),
+ Common::String::format("%02d h %02d", td.tm_hour, td.tm_min));
+}
+
+static int mapSaveSlotY(int y, bool splitV, int tileY) {
+ int splitLine = (tileY + 1) * 256;
+ if (splitV && y >= splitLine)
+ return (tileY + 3) * 256 + y - splitLine;
+ return y;
+}
+
+static void fillSaveSlotRect(Graphics::Surface &dst, const Common::Rect &rect, uint32 color, bool splitV, int tileY) {
+ if (splitV) {
+ int splitLine = (tileY + 1) * 256;
+ int topH = CLIP<int>(splitLine - rect.top, 0, rect.height());
+ if (topH > 0) {
+ Common::Rect top = rect;
+ top.bottom = rect.top + topH;
+ dst.fillRect(top, color);
+ }
+ if (topH < rect.height()) {
+ int bottomY = (tileY + 3) * 256 + MAX(rect.top - splitLine, 0);
+ Common::Rect bottom(rect.left, bottomY, rect.right, bottomY + rect.height() - topH);
+ dst.fillRect(bottom, color);
+ }
+ } else {
+ dst.fillRect(rect, color);
+ }
+}
+
+static int drawSaveTextBlock(Graphics::Surface &dst, const Graphics::Font *font, const Common::String &text,
+ int x, int y, int width, uint32 color, Graphics::TextAlign align, int lineHeight, bool splitV, int tileY,
+ bool reserveEmptyFinalLine = false) {
+ bool hasText = false;
+ for (uint i = 0; i < text.size(); ++i) {
+ if (text[i] != '\n' && text[i] != '\0') {
+ hasText = true;
+ break;
+ }
+ }
+ if (!hasText)
+ return y;
+
+ uint start = 0;
+ for (uint i = 0; i < text.size(); ++i) {
+ if (text[i] == '\n' || text[i] == '\0') {
+ if (i > start) {
+ Common::String line;
+ for (uint j = start; j < i; ++j)
+ line += text[j];
+ font->drawString(&dst, line, x, mapSaveSlotY(y, splitV, tileY), width, color, align);
+ y += lineHeight;
+ } else if (reserveEmptyFinalLine && i == text.size() - 1) {
+ y += lineHeight;
+ }
+ start = i + 1;
+ }
+ }
+ if (start < text.size()) {
+ Common::String line;
+ for (uint j = start; j < text.size(); ++j)
+ line += text[j];
+ font->drawString(&dst, line, x, mapSaveSlotY(y, splitV, tileY), width, color, align);
+ y += lineHeight;
+ }
+
+ return y;
+}
+
+static int saveCardTileId(int face, int x, int y) {
+ return (face << 2) + ((y < 256) ? (x < 256 ? 0 : 1) : (x < 256 ? 3 : 2));
+}
+
+static void copyCubeFaceToSurface(Graphics::ManagedSurface &faceSurface, const Graphics::Surface &vrSurface, int face) {
+ for (int y = 0; y < 512; ++y) {
+ for (int x = 0; x < 512; ++x) {
+ const int tileId = saveCardTileId(face, x, y);
+ faceSurface.setPixel(x, y, vrSurface.getPixel(x & 0xff, (tileId << 8) + (y & 0xff)));
+ }
+ }
+}
+
+static void copySurfaceToCubeFace(Graphics::Surface &vrSurface, const Graphics::ManagedSurface &faceSurface, int face) {
+ for (int y = 0; y < 512; ++y) {
+ for (int x = 0; x < 512; ++x) {
+ const int tileId = saveCardTileId(face, x, y);
+ vrSurface.setPixel(x & 0xff, (tileId << 8) + (y & 0xff), faceSurface.getPixel(x, y));
+ }
+ }
+}
+
+static void projectSaveCard(Graphics::ManagedSurface &faceSurface, const Graphics::ManagedSurface &card, float angle) {
+ struct Vertex {
+ float x;
+ float y;
+ float invW;
+ float uOverW;
+ float vOverW;
+ };
+
+ const float srcW = static_cast<float>(card.w);
+ const float srcH = static_cast<float>(card.h);
+ const float distance = srcW / 8.0f + srcW * 8.0f / 6.283100128173828f;
+ const float cosA = cosf(angle);
+ const float sinA = sinf(angle);
+
+ auto makeVertex = [&](float modelU, float modelV, float textureU, float textureV) {
+ const float modelX = modelU - srcW / 2.0f;
+ const float modelY = distance;
+ const float modelZ = srcH / 2.0f - modelV + 32.0f;
+ const float projectedW = (modelX * sinA - modelY * cosA) / 256.0f;
+ const float invW = 1.0f / projectedW;
+
+ Vertex vertex;
+ vertex.x = (modelX * (cosA + sinA) + modelY * (sinA - cosA)) * invW;
+ vertex.y = (modelX * sinA - modelY * cosA - modelZ) * invW;
+ vertex.invW = invW;
+ vertex.uOverW = textureU * invW;
+ vertex.vOverW = textureV * invW;
+ return vertex;
+ };
+
+ Vertex vertices[4] = {
+ makeVertex(0.0f, 0.0f, srcW, srcH),
+ makeVertex(static_cast<float>(card.w), 0.0f, 0.0f, srcH),
+ makeVertex(static_cast<float>(card.w), static_cast<float>(card.h), 0.0f, 0.0f),
+ makeVertex(0.0f, static_cast<float>(card.h), srcW, 0.0f)
+ };
+
+ auto rasterizeTriangle = [&](const Vertex &a, const Vertex &b, const Vertex &c) {
+ const float area = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
+ if (ABS(area) < 0.0001f)
+ return;
+
+ int minX = CLIP<int>(static_cast<int>(floorf(MIN(a.x, MIN(b.x, c.x)))), 0, faceSurface.w - 1);
+ int maxX = CLIP<int>(static_cast<int>(ceilf(MAX(a.x, MAX(b.x, c.x)))), 0, faceSurface.w - 1);
+ int minY = CLIP<int>(static_cast<int>(floorf(MIN(a.y, MIN(b.y, c.y)))), 0, faceSurface.h - 1);
+ int maxY = CLIP<int>(static_cast<int>(ceilf(MAX(a.y, MAX(b.y, c.y)))), 0, faceSurface.h - 1);
+
+ for (int y = minY; y <= maxY; ++y) {
+ for (int x = minX; x <= maxX; ++x) {
+ const float px = static_cast<float>(x) + 0.5f;
+ const float py = static_cast<float>(y) + 0.5f;
+ const float w0 = ((b.x - px) * (c.y - py) - (b.y - py) * (c.x - px)) / area;
+ const float w1 = ((c.x - px) * (a.y - py) - (c.y - py) * (a.x - px)) / area;
+ const float w2 = 1.0f - w0 - w1;
+ if (w0 < 0.0f || w1 < 0.0f || w2 < 0.0f)
+ continue;
+
+ const float invW = w0 * a.invW + w1 * b.invW + w2 * c.invW;
+ if (ABS(invW) < 0.0001f)
+ continue;
+ const float u = (w0 * a.uOverW + w1 * b.uOverW + w2 * c.uOverW) / invW;
+ const float v = (w0 * a.vOverW + w1 * b.vOverW + w2 * c.vOverW) / invW;
+ if (u < 0.0f || u > srcW || v < 0.0f || v > srcH)
+ continue;
+
+ const int srcX = CLIP<int>(static_cast<int>(floorf(u)), 0, card.w - 1);
+ const int srcY = CLIP<int>(static_cast<int>(floorf(v)), 0, card.h - 1);
+ faceSurface.setPixel(x, y, card.getPixel(srcX, srcY));
+ }
+ }
+ };
+
+ rasterizeTriangle(vertices[0], vertices[1], vertices[2]);
+ rasterizeTriangle(vertices[0], vertices[2], vertices[3]);
+}
+
PhoenixVREngine::PhoenixVREngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst),
_frameLimiter(g_system, kFPSLimit),
_gameDescription(gameDesc),
_randomSource("PhoenixVR"),
_rgb565(2, 5, 6, 5, 0, 11, 5, 0, 0),
- _thumbnail(139, 103, _rgb565),
+ _thumbnail(isAmerzoneGame(gameDesc) ? 232 : 139, isAmerzoneGame(gameDesc) ? 174 : 103, _rgb565),
_lockKey(13),
_fov(kPi2),
_angleX(0),
@@ -1415,14 +1640,16 @@ Common::Error PhoenixVREngine::saveGameStream(Common::WriteStream *slot, bool is
GameState state;
state.script = _contextScript;
state.game = _contextLabel;
-
- static const char *wday[] = {
- "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
+ const bool isAmerzone = gameIdMatches("amerzone");
+ Common::String amerzoneLevelLabel;
+ if (isAmerzone) {
+ amerzoneLevelLabel = getAmerzoneLevelLabel(state.script);
+ state.game.clear();
+ }
TimeDate td = {};
g_system->getTimeAndDate(td);
- // Saturday 03 01 2026[\x00]23 h 17
- state.info = Common::String::format("%s %02d %02d %04d%c%02d h %02d", wday[td.tm_wday], td.tm_mday, td.tm_mon + 1, td.tm_year + 1900, 0, td.tm_hour, td.tm_min);
+ state.info = formatSaveInfo(td, isAmerzone, amerzoneLevelLabel);
state.thumbWidth = _thumbnail.w;
state.thumbHeight = _thumbnail.h;
@@ -1458,11 +1685,60 @@ Common::Error PhoenixVREngine::saveGameStream(Common::WriteStream *slot, bool is
return Common::kNoError;
}
+void PhoenixVREngine::drawSaveCard(int idx) {
+ if (!gameIdMatches("amerzone")) {
+ static const int faces[] = {4, 3, 5, 1};
+ const int face = faces[(idx - 1) / 2];
+ const bool odd = (idx - 1) & 1;
+ drawSlot(idx, face, odd ? 275 : 97, 200);
+ return;
+ }
+
+ Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
+ if (!slot)
+ return;
+
+ auto state = GameState::load(*slot);
+ auto &dst = _vr.getSurface();
+ Graphics::Surface *thumbnail = state.getThumbnail(dst.format, 232);
+ const int cardW = thumbnail->w + 6;
+ const int cardH = thumbnail->w + 30;
+
+ Graphics::ManagedSurface card(cardW, cardH, dst.format);
+ const uint32 white = dst.format.RGBToColor(0xff, 0xff, 0xff);
+ const uint32 black = dst.format.RGBToColor(0, 0, 0);
+ card.fillRect(Common::Rect(0, 0, cardW, cardH), white);
+ card.fillRect(Common::Rect(0, 0, cardW, 1), black);
+ card.fillRect(Common::Rect(0, cardH - 1, cardW, cardH), black);
+ card.fillRect(Common::Rect(0, 0, 1, cardH), black);
+ card.fillRect(Common::Rect(cardW - 1, 0, cardW, cardH), black);
+ card.copyRectToSurface(*thumbnail, 3, 6, thumbnail->getRect());
+
+ const Graphics::Font *font = getFont(16, true);
+ if (font) {
+ int textY = thumbnail->h + 18;
+ textY = drawSaveTextBlock(*card.surfacePtr(), font, state.game, 0, textY, cardW, black, Graphics::kTextAlignCenter, 18, false, 0);
+ drawSaveTextBlock(*card.surfacePtr(), font, state.info, 0, textY, cardW, black, Graphics::kTextAlignCenter, 18, false, 0);
+ }
+
+ static const int faces[] = {4, 3, 5, 1};
+ const int face = faces[(idx - 1) / 2];
+ const float angle = ((idx - 1) & 1) ? -kPi / 8.0f : kPi / 8.0f;
+ Graphics::ManagedSurface faceSurface(512, 512, dst.format);
+ copyCubeFaceToSurface(faceSurface, dst, face);
+ projectSaveCard(faceSurface, card, angle);
+ copySurfaceToCubeFace(dst, faceSurface, face);
+
+ thumbnail->free();
+ delete thumbnail;
+}
+
void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
Common::ScopedPtr<Common::InSaveFile> slot(_saveFileMan->openForLoading(getSaveStateName(idx)));
if (!slot)
return;
auto state = GameState::load(*slot);
+ const bool isAmerzone = gameIdMatches("amerzone");
y += face * 4 * 256;
bool splitV = true;
@@ -1473,8 +1749,24 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
}
auto &dst = _vr.getSurface();
- auto *src = state.getThumbnail(dst.format);
+ auto *src = state.getThumbnail(dst.format, isAmerzone ? 232 : 0);
int tileY = y / 256;
+ if (isAmerzone) {
+ const int cardX = x - 3;
+ const int cardY = y - 6;
+ const int cardW = src->w + 6;
+ const int cardH = src->w + 30;
+ uint32 white = dst.format.RGBToColor(0xff, 0xff, 0xff);
+ uint32 black = dst.format.RGBToColor(0, 0, 0);
+ fillSaveSlotRect(dst, Common::Rect(cardX, cardY, cardX + cardW, cardY + cardH), white, splitV, tileY);
+ fillSaveSlotRect(dst, Common::Rect(cardX, cardY, cardX + cardW, cardY + 1), black, splitV, tileY);
+ fillSaveSlotRect(dst, Common::Rect(cardX, cardY + cardH - 1, cardX + cardW, cardY + cardH), black, splitV, tileY);
+ fillSaveSlotRect(dst, Common::Rect(cardX, cardY, cardX + 1, cardY + cardH), black, splitV, tileY);
+ fillSaveSlotRect(dst, Common::Rect(cardX + cardW - 1, cardY, cardX + cardW, cardY + cardH), black, splitV, tileY);
+ x = cardX + 3;
+ y = cardY + 6;
+ tileY = y / 256;
+ }
auto srcRect = src->getRect();
short srcSplitY = MIN(y + src->h, (tileY + 1) * 256) - y;
if (splitV)
@@ -1485,12 +1777,26 @@ void PhoenixVREngine::drawSlot(int idx, int face, int x, int y) {
srcRect.bottom = src->h;
dst.copyRectToSurface(*src, x, (tileY + 3) * 256, srcRect);
}
- auto *font = getFont(12, false);
- static int kMargin = 14;
+ auto *font = getFont(isAmerzone ? 10 : 12, isAmerzone);
if (font) {
auto color = dst.format.RGBToColor(0, 0, 0);
- auto dstY = splitV ? (tileY + 3) * 256 - srcSplitY : y;
- font->drawString(&dst, state.info, x, dstY + kMargin + src->h, src->w, color, Graphics::TextAlign::kTextAlignCenter);
+ int textX = x;
+ int textW = src->w;
+ Graphics::TextAlign textAlign = Graphics::kTextAlignLeft;
+ int textY = y + 0x72;
+ int lineHeight = 14;
+ if (isAmerzone) {
+ textX = x - 3;
+ textW = src->w + 6;
+ textY = y - 6 + src->h + 14;
+ textAlign = Graphics::kTextAlignCenter;
+
+ textY = drawSaveTextBlock(dst, font, state.game, textX, textY, textW, color, textAlign, lineHeight, splitV, tileY);
+ drawSaveTextBlock(dst, font, state.info, textX, textY, textW, color, textAlign, lineHeight, splitV, tileY);
+ } else {
+ textY = drawSaveTextBlock(dst, font, state.game, textX, textY, textW, color, textAlign, lineHeight, splitV, tileY, true);
+ drawSaveTextBlock(dst, font, state.info, textX, textY, textW, color, textAlign, lineHeight, splitV, tileY);
+ }
}
src->free();
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index 07f411c38c9..f5928c5328b 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -175,6 +175,7 @@ public:
Common::Error loadGameStream(Common::SeekableReadStream *stream) override;
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override;
void drawSlot(int idx, int face, int x, int y);
+ void drawSaveCard(int idx);
void captureContext();
void setContextLabel(const Common::String &contextLabel) {
Commit: e07b3297ab63c2759d7c4077d948a7c8a356e47b
https://github.com/scummvm/scummvm/commit/e07b3297ab63c2759d7c4077d948a7c8a356e47b
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
PHOENIXVR: Fix Amerzone save room level restore
Changed paths:
engines/phoenixvr/commands.h
engines/phoenixvr/phoenixvr.cpp
engines/phoenixvr/phoenixvr.h
diff --git a/engines/phoenixvr/commands.h b/engines/phoenixvr/commands.h
index 4750a3538e2..ca03d986cd7 100644
--- a/engines/phoenixvr/commands.h
+++ b/engines/phoenixvr/commands.h
@@ -548,6 +548,15 @@ struct LoadSave : public Script::Command {
} else if (n == 2) {
auto &srcVar = args[0];
auto &dstVar = args[1];
+ if (!dstVar.empty() && Common::isDigit(dstVar[0])) {
+ uint level = atoi(dstVar.c_str());
+ uint currentLevel = g_engine->currentAmerzoneLevel();
+ if (currentLevel != 0) {
+ g_engine->setVariable(srcVar, currentLevel == level);
+ return;
+ }
+ }
+
auto value = g_engine->getVariable(srcVar);
g_engine->setVariable(srcVar, 0);
g_engine->setVariable(dstVar, value);
diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index ab34e347fb7..58672d84f74 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -341,6 +341,20 @@ bool PhoenixVREngine::gameIdMatches(const char *gameId) const {
return strcmp(_gameDescription->gameId, gameId) == 0;
}
+uint PhoenixVREngine::currentAmerzoneLevel() const {
+ if (!gameIdMatches("amerzone"))
+ return 0;
+
+ uint index = 0;
+ for (const Common::String &level : _levels) {
+ ++index;
+ if (_contextScript.hasPrefixIgnoreCase(level))
+ return index;
+ }
+
+ return _currentLevel;
+}
+
Common::String PhoenixVREngine::removeDrive(const Common::String &path) {
if (path.size() < 2 || path[1] != ':')
return path;
@@ -1613,7 +1627,7 @@ Common::Error PhoenixVREngine::loadGameStream(Common::SeekableReadStream *slot)
auto &level = _levels[i];
if (state.script.hasPrefixIgnoreCase(level)) {
debug("current level is %u", i);
- _currentLevel = i + 1;
+ _currentLevel = i;
break;
}
}
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index f5928c5328b..0860fe659c2 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -186,6 +186,7 @@ public:
bool wasRestarted() const { return _restarted; }
bool wasLoaded() const { return _loaded; }
+ uint currentAmerzoneLevel() const;
void saveVariables();
void loadVariables();
Commit: 3d68656634218d213a8aa3912573c85cc2d55850
https://github.com/scummvm/scummvm/commit/3d68656634218d213a8aa3912573c85cc2d55850
Author: Scorp (scorp at mrs.mn)
Date: 2026-06-11T23:10:30+01:00
Commit Message:
PHOENIXVR: Add more game variants
Changed paths:
engines/phoenixvr/detection_tables.h
diff --git a/engines/phoenixvr/detection_tables.h b/engines/phoenixvr/detection_tables.h
index efe84a36f11..b733ed3b108 100644
--- a/engines/phoenixvr/detection_tables.h
+++ b/engines/phoenixvr/detection_tables.h
@@ -128,8 +128,8 @@ const ADGameDescription gameDescriptions[] = {
{"lochness",
nullptr,
AD_ENTRY2s("script.pak", "a7ee3aae653658f93bba7f237bcf06f3", 1904,
- "textes.txt", "3f2deea06efed98a355ea03d69fd15ce", 1608),
- Common::EN_USA,
+ "textes.txt", "d1546d04243ee63f9ff6c5fc551082e1", 1763),
+ Common::RU_RUS,
Common::kPlatformWindows,
ADGF_DROPPLATFORM | ADGF_CD,
GUIO1(GUIO_NONE)}
More information about the Scummvm-git-logs
mailing list