[Scummvm-git-logs] scummvm master -> 3474234780145c5014bfd9dedcdd44f232a6ed41
neuromancer
noreply at scummvm.org
Sat Sep 12 11:54:44 UTC 2026
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
9521ce0db5 FREESCAPE: allow to load 3DCK for atari
4ccc9ad054 FREESCAPE: added Egyptman for atari
cebd12bbf1 SCUMM: RA2: potential fix for L2 enemy partial decoding
e973f7e7fb SCUMM: RA2: added missing blur effect affecting at least L2
3474234780 SCUMM: RA2: properly implement pause menu including film effect
Commit: 9521ce0db5639a9fe9a418ca9e593e6ddfdeb19c
https://github.com/scummvm/scummvm/commit/9521ce0db5639a9fe9a418ca9e593e6ddfdeb19c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-12T12:21:36+02:00
Commit Message:
FREESCAPE: allow to load 3DCK for atari
Changed paths:
A engines/freescape/games/3dck/atari.cpp
engines/freescape/area.cpp
engines/freescape/area.h
engines/freescape/detection.cpp
engines/freescape/games/3dck/3dck.cpp
engines/freescape/games/3dck/3dck.h
engines/freescape/games/3dck/ui.cpp
engines/freescape/gfx_opengl_shaders.cpp
engines/freescape/language/detokeniser.h
engines/freescape/language/detokeniser_3dck16.cpp
engines/freescape/language/execution_3dck16.cpp
engines/freescape/module.mk
diff --git a/engines/freescape/area.cpp b/engines/freescape/area.cpp
index 5c53b8148b8..3abe174107a 100644
--- a/engines/freescape/area.cpp
+++ b/engines/freescape/area.cpp
@@ -880,13 +880,18 @@ void Area::addGroupFromArea(int16 id, Area *global) {
}
-void Area::addFloor() {
+void Area::addFloor(uint8 extraColor) {
_hasSyntheticFloor = true;
int id = 0;
assert(!_objectsByID->contains(id));
Common::Array<uint8> *gColors = new Common::Array<uint8>;
for (int i = 0; i < 6; i++)
gColors->push_back(_groundColor);
+ Common::Array<uint8> *extraColors = nullptr;
+ if (extraColor) {
+ extraColors = new Common::Array<uint8>();
+ extraColors->resize(6, extraColor);
+ }
int maxSize = 10000000 / 4;
Object *obj = (Object *)new GeometricObject(
@@ -896,7 +901,7 @@ void Area::addFloor() {
Math::Vector3d(-maxSize, -3, -maxSize), // Position
Math::Vector3d(maxSize * 4, 3, maxSize * 4), // size
gColors,
- nullptr,
+ extraColors,
nullptr,
FCLInstructionVector());
(*_objectsByID)[id] = obj;
diff --git a/engines/freescape/area.h b/engines/freescape/area.h
index 5dc65ecb921..bfacfe8135b 100644
--- a/engines/freescape/area.h
+++ b/engines/freescape/area.h
@@ -79,7 +79,7 @@ public:
void addObjectFromArea(int16 id, Area *global);
void addGroupFromArea(int16 id, Area *global);
void addObject(Object *obj);
- void addFloor();
+ void addFloor(uint8 extraColor = 0);
void addStructure(Area *global);
void removeObject(int16 id);
void resetArea();
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 0b2765dfeb7..75a8fe63c0e 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1159,6 +1159,46 @@ const ADGameDescription gameDescriptions[] = {
GUIO3(GUIO_NOMIDI, GUIO_RENDEREGA, GAMEOPTION_WASD_CONTROLS)
},
// 3D Construction Kit games
+ {
+ "3dkit",
+ "The Maze",
+ AD_ENTRY2s("THEMAZE.DAT", "0925b7be5b3644cbf725a8ed747beecc", 85162,
+ "THEMAZE.BDR", "ecee4abd508e34df25be8607fb780771", 32128),
+ Common::EN_ANY,
+ Common::kPlatformAtariST,
+ ADGF_UNSTABLE,
+ GUIO4(GUIO_NOMIDI, GUIO_NOMUSIC, GUIO_NOSFX, GUIO_RENDERATARIST)
+ },
+ {
+ "3dkit",
+ "Anarchy Academy",
+ AD_ENTRY2s("RUNNER.DAT", "1cf7c049ee59503dd7218b0f45828132", 42802,
+ "RUNNER.BDR", "2ae0a6e768595d8ef325f8ef14dc9525", 32128),
+ Common::EN_ANY,
+ Common::kPlatformAtariST,
+ ADGF_UNSTABLE,
+ GUIO4(GUIO_NOMIDI, GUIO_NOMUSIC, GUIO_NOSFX, GUIO_RENDERATARIST)
+ },
+ {
+ "3dkit",
+ "Hysula",
+ AD_ENTRY2s("HYSULA.DAT", "6db092c4a6cc56d562b14e1c7219005b", 69472,
+ "HYSULA.BDR", "79e5b9ac9bb679278f64ef0568b50bfe", 32128),
+ Common::EN_ANY,
+ Common::kPlatformAtariST,
+ ADGF_UNSTABLE,
+ GUIO4(GUIO_NOMIDI, GUIO_NOMUSIC, GUIO_NOSFX, GUIO_RENDERATARIST)
+ },
+ {
+ "3dkit",
+ "Supernova",
+ AD_ENTRY2s("SUPNOV.DAT", "a4049142da498339198eac4082f1616d", 72922,
+ "SUPNOV.BDR", "ffad88a56fd096de77edb976dd7d4825", 32128),
+ Common::EN_ANY,
+ Common::kPlatformAtariST,
+ ADGF_UNSTABLE,
+ GUIO4(GUIO_NOMIDI, GUIO_NOMUSIC, GUIO_NOSFX, GUIO_RENDERATARIST)
+ },
{
"3dkit",
"Dead by Dawn",
@@ -1426,16 +1466,6 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
- {
- "3dkit",
- "Anarchy Academy",
- AD_ENTRY2s("ANARCHY.PRG", "78d543be4aad9608093c1e91bc100307", 270,
- "RUNNER.DAT", "1cf7c049ee59503dd7218b0f45828132", 42802),
- Common::EN_ANY,
- Common::kPlatformAtariST,
- ADGF_UNSUPPORTED,
- GUIO1(GUIO_NOMIDI)
- },
{
"3dkit",
"The World of Stairs",
diff --git a/engines/freescape/games/3dck/3dck.cpp b/engines/freescape/games/3dck/3dck.cpp
index 0cbb56f3b42..948e01308ed 100644
--- a/engines/freescape/games/3dck/3dck.cpp
+++ b/engines/freescape/games/3dck/3dck.cpp
@@ -80,7 +80,7 @@ static void readColors(Common::SeekableReadStream &file, byte &first, byte &seco
}
KitEngine::KitEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngine(syst, gd), _initialPlayerHeight(0) {
- _renderMode = Common::kRenderVGA;
+ _renderMode = isAtariST() ? Common::kRenderAtariST : Common::kRenderVGA;
_screenW = 320;
_screenH = 200;
_fullscreenViewArea = Common::Rect(_screenW, _screenH);
@@ -92,6 +92,17 @@ KitEngine::KitEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEngi
}
void KitEngine::loadAssets() {
+ if (isAtariST())
+ loadAssetsAtari();
+ else
+ loadAssetsDOS();
+ _gfx->_palette = _palette;
+ _gfx->_keyColor = 0;
+ _scriptSurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
+ _scriptSurface.fillRect(_fullscreenViewArea, 0);
+}
+
+void KitEngine::loadAssetsDOS() {
Common::File file;
if (!file.open(_gameDescription->filesDescriptions[0].fileName))
error("Unable to open 3D Construction Kit RUN file");
@@ -129,10 +140,6 @@ void KitEngine::loadAssets() {
_border->setPalette(_palette, 0, 256);
// Preserve the full VGA palette during shared border processing.
_border->convertToInPlace(_gfx->_texturePixelFormat);
- _gfx->_palette = _palette;
- _gfx->_keyColor = 0;
- _scriptSurface.create(_screenW, _screenH, _gfx->_texturePixelFormat);
- _scriptSurface.fillRect(_fullscreenViewArea, 0);
uint16 soundSize = readBlockSize(file);
Common::SeekableSubReadStream sounds(&file, file.pos(), file.pos() + soundSize);
loadSounds(sounds);
@@ -141,7 +148,9 @@ void KitEngine::loadAssets() {
void KitEngine::loadWorld(Common::SeekableReadStream &file) {
requireBytes(file, 500);
uint32 signature = file.readUint32BE();
- if (signature != MKTAG('C', 'P', 0, 0) && signature != MKTAG('C', 'P', '0', '1'))
+ bool validSignature = isAtariST() ? signature == MKTAG('A', 'M', '0', '1') :
+ signature == MKTAG('C', 'P', 0, 0) || signature == MKTAG('C', 'P', '0', '1');
+ if (!validSignature)
error("Unsupported 3D Construction Kit world format");
uint16 areaCount = file.readUint16BE();
uint32 globalConditions = 2 * file.readUint16BE();
@@ -155,15 +164,20 @@ void KitEngine::loadWorld(Common::SeekableReadStream &file) {
int top = _screenH - 1 - centerY - halfHeight;
_viewArea = Common::Rect(centerX - halfWidth, top, centerX + halfWidth, top + 2 * halfHeight);
- // RUNVGA derives its projection scales from the viewport dimensions.
- int xScale = 74 * (_viewArea.height() - 1) / 256;
- int yScale = 55 * (_viewArea.width() - 1) / 256;
- if (!xScale || !yScale)
+ int xScale = file.readUint16BE();
+ int yScale = file.readUint16BE();
+ int zScale = file.readUint16BE();
+ if (isDOS()) {
+ // RUNVGA derives its projection scales from the viewport dimensions.
+ xScale = 74 * (_viewArea.height() - 1) / 256;
+ yScale = 55 * (_viewArea.width() - 1) / 256;
+ zScale = 24;
+ }
+ if (!xScale || !yScale || !zScale)
error("3D Construction Kit viewport is too small");
- _fieldOfView = 2.0f * Math::rad2deg(atan(24.0f / xScale));
+ _fieldOfView = 2.0f * Math::rad2deg(atan(float(zScale) / xScale));
_viewAspectRatio = float(yScale) / xScale;
- file.skip(6);
_timerInterval = file.readUint16BE();
_activationRange = file.readUint16BE();
_maxFallingDistance = file.readUint16BE();
@@ -183,9 +197,10 @@ void KitEngine::loadWorld(Common::SeekableReadStream &file) {
_angleRotations.push_back(angle);
_angleRotationIndex = 0;
- file.skip(2);
// DOS word offsets wrap at 64 KiB.
- uint32 indicatorOffset = uint16(2 * file.readUint16BE());
+ uint32 indicatorOffset = 2 * file.readUint32BE();
+ if (isDOS())
+ indicatorOffset = uint16(indicatorOffset);
uint16 indicatorCount = file.readUint16BE();
_initialCondition = file.readUint16BE();
if (indicatorOffset > uint32(file.size()) || (!indicatorOffset && indicatorCount))
@@ -202,7 +217,9 @@ void KitEngine::loadWorld(Common::SeekableReadStream &file) {
requireBytes(file, uint32(areaCount) * 4);
Common::Array<uint32> areaOffsets;
for (uint i = 0; i < areaCount; i++) {
- uint32 offset = uint16(2 * file.readUint32BE());
+ uint32 offset = 2 * file.readUint32BE();
+ if (isDOS())
+ offset = uint16(offset);
if (indicatorCount && offset >= indicatorOffset && offset < indicatorOffset + 2 * _indicatorData.size()) {
warning("Ignoring stale 3D Construction Kit area offset %u into indicator data", offset);
continue;
@@ -246,7 +263,7 @@ Common::Array<KitEngine::ConditionData> KitEngine::loadConditions(Common::Seekab
uint16 words = file.readUint16BE() & 0x7fff;
ConditionData condition;
condition.name = name;
- Common::String source = detokeniseKit16Condition(readCode(file, 2 * words), condition.condition);
+ Common::String source = detokeniseKit16Condition(readCode(file, 2 * words), condition.condition, isAtariST());
debugC(1, kFreescapeDebugParser, "3DCK condition %s:\n%s", name, source.c_str());
conditions.push_back(condition);
}
@@ -261,8 +278,10 @@ Area *KitEngine::loadArea(Common::SeekableReadStream &file) {
uint16 id = file.readUint16BE();
if (_areaMap.contains(id))
error("Duplicate 3D Construction Kit area %u", id);
- file.skip(2);
- uint32 conditions = start + 2 * file.readUint16BE();
+ uint32 conditionOffset = file.readUint32BE();
+ if (isDOS())
+ conditionOffset = uint16(conditionOffset);
+ uint32 conditions = start + 2 * conditionOffset;
uint16 scale = file.readUint16BE();
uint16 sky = file.readUint16BE();
uint16 ground = file.readUint16BE();
@@ -305,9 +324,14 @@ Area *KitEngine::loadArea(Common::SeekableReadStream &file) {
area->_groundColor = ((ground & 0xf) << 4) | ((ground >> 8) & 0xf);
area->_usualBackgroundColor = 0;
area->_underFireBackgroundColor = 0;
+ byte groundExtraColor = 0;
+ if (isAtariST()) {
+ splitColorAtari(area->_skyColor, data.skyExtraColor);
+ splitColorAtari(area->_groundColor, groundExtraColor);
+ }
// The runner supplies a default floor at Y=0.
if (id != 255)
- area->addFloor();
+ area->addFloor(groundExtraColor);
return area;
}
@@ -335,6 +359,7 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
bool geometric = data.type >= kCubeType && data.type <= kHexagonType && data.type != kSensorType;
Common::Array<uint8> *colors = nullptr;
+ Common::Array<uint8> *extraColors = nullptr;
Common::Array<float> *ordinates = nullptr;
if (geometric) {
ObjectType type = ObjectType(data.type);
@@ -347,9 +372,18 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
return nullptr;
}
colors = new Common::Array<uint8>();
+ if (isAtariST())
+ extraColors = new Common::Array<uint8>();
for (int i = 0; i < colorCount; i += 2) {
byte first, second;
readColors(payload, first, second);
+ if (extraColors) {
+ byte extraFirst, extraSecond;
+ splitColorAtari(first, extraFirst);
+ splitColorAtari(second, extraSecond);
+ extraColors->push_back(extraFirst);
+ extraColors->push_back(extraSecond);
+ }
colors->push_back(first);
colors->push_back(second);
}
@@ -358,6 +392,11 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
const byte sides[] = {(*colors)[2], (*colors)[0], (*colors)[3], (*colors)[1]};
for (uint i = 0; i < ARRAYSIZE(sides); i++)
(*colors)[i] = sides[i];
+ if (extraColors) {
+ const byte extraSides[] = {(*extraColors)[2], (*extraColors)[0], (*extraColors)[3], (*extraColors)[1]};
+ for (uint i = 0; i < ARRAYSIZE(extraSides); i++)
+ (*extraColors)[i] = extraSides[i];
+ }
}
if (ordinateCount) {
requireBytes(payload, 2 * ordinateCount);
@@ -388,7 +427,7 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
// Entrances can retain editor data after their header.
data.extra = readWords(payload, (payload.size() - payload.pos()) / 2);
} else {
- Common::String source = detokeniseKit16Condition(readCode(payload, payload.size() - payload.pos()), data.condition);
+ Common::String source = detokeniseKit16Condition(readCode(payload, payload.size() - payload.pos()), data.condition, isAtariST());
debugC(1, kFreescapeDebugParser, "3DCK object %u condition:\n%s", data.id, source.c_str());
}
file.seek(end);
@@ -407,7 +446,7 @@ Object *KitEngine::loadObject(Common::SeekableReadStream &file, ObjectData &data
(*ordinates)[i] += data.initialOrigin.getValue(i % 3);
}
return new GeometricObject(type, data.id, (data.flags & kKitInitiallyInvisible) ? 0x80 : 0,
- data.initialOrigin, data.size, colors, nullptr, ordinates, FCLInstructionVector());
+ data.initialOrigin, data.size, colors, extraColors, ordinates, FCLInstructionVector());
}
void KitEngine::initGameState() {
@@ -424,6 +463,10 @@ void KitEngine::gotoArea(uint16 areaID, int entranceID) {
if (_currentArea)
_kitVariables[9] = _currentArea->getAreaID();
_currentArea = _areaMap[areaID];
+ if (isAtariST()) {
+ _gfx->_palette = _areaData[areaID].palette;
+ updateBorderAtari();
+ }
Entrance *entrance = static_cast<Entrance *>(_currentArea->entranceWithID(entranceID));
if (entrance) {
_position = entrance->getOrigin();
diff --git a/engines/freescape/games/3dck/3dck.h b/engines/freescape/games/3dck/3dck.h
index 49695fee625..53f3c021c59 100644
--- a/engines/freescape/games/3dck/3dck.h
+++ b/engines/freescape/games/3dck/3dck.h
@@ -37,6 +37,7 @@ public:
void checkIfStillInArea() override;
bool checkIfGameEnded() override;
void borderScreen() override {}
+ void drawBackground() override;
void drawUI() override;
bool handleInput(const Common::Event &event) override;
void updatePlayerMovement(float deltaTime) override;
@@ -88,6 +89,8 @@ private:
Common::HashMap<uint16, ObjectData> objects;
Common::Array<uint16> objectOrder;
Common::Array<ConditionData> conditions;
+ byte palette[16 * 3] = {};
+ byte skyExtraColor = 0;
};
struct ScriptEntry {
@@ -96,6 +99,11 @@ private:
ScriptEntry(ScriptState *s, bool r) : script(s), resume(r) {}
};
+ void loadAssetsDOS();
+ void loadAssetsAtari();
+ void loadPaletteAtari(Common::SeekableReadStream &file, byte *palette);
+ void updateBorderAtari();
+ void splitColorAtari(byte &color, byte &extraColor);
void loadWorld(Common::SeekableReadStream &file);
void loadSounds(Common::SeekableReadStream &file);
void playPendingSound();
@@ -147,14 +155,14 @@ private:
void interact(bool shot);
void printMessage(uint16 indicator, const Common::String &message);
void updateIndicators();
- uint32 indicatorColor(byte color) const;
+ uint32 indicatorColor(byte color, int y) const;
Common::HashMap<uint16, AreaData> _areaData;
Common::Array<ConditionData> _globalConditions;
Common::Array<uint16> _indicatorData;
Common::Array<uint16> _controlData;
- byte _palette[256 * 3];
+ byte _palette[256 * 3] = {};
uint16 _initialPlayerHeight;
uint16 _initialCondition = 0, _timerInterval = 0, _activationRange = 0;
uint32 _kitVariables[256] = {};
@@ -169,6 +177,7 @@ private:
Common::Array<ScriptEntry> _scriptQueue;
Common::Array<ScriptState *> _suspendedScripts;
Graphics::ManagedSurface _scriptSurface;
+ Graphics::ManagedSurface _borderPixels;
};
} // namespace Freescape
diff --git a/engines/freescape/games/3dck/atari.cpp b/engines/freescape/games/3dck/atari.cpp
new file mode 100644
index 00000000000..aa74f929c8f
--- /dev/null
+++ b/engines/freescape/games/3dck/atari.cpp
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/memstream.h"
+#include "image/neo.h"
+
+#include "freescape/games/3dck/3dck.h"
+
+namespace Freescape {
+
+void KitEngine::loadPaletteAtari(Common::SeekableReadStream &file, byte *palette) {
+ if (file.pos() > file.size() || file.size() - file.pos() < 32)
+ error("Truncated 3D Construction Kit Atari ST palette");
+ for (int i = 0; i < 16; i++) {
+ uint16 color = file.readUint16BE();
+ // The runner converts RGB444 to the ST's three bits per component.
+ for (int c = 0; c < 3; c++)
+ palette[3 * i + c] = ((color >> (9 - 4 * c)) & 7) * 255 / 7;
+ }
+}
+
+void KitEngine::loadAssetsAtari() {
+ Common::File file;
+ if (!file.open(_gameDescription->filesDescriptions[0].fileName))
+ error("Unable to open 3D Construction Kit Atari ST data");
+ if (file.size() < 512 || (file.size() & 1))
+ error("Invalid 3D Construction Kit Atari ST data size");
+ Common::Array<byte> data;
+ data.resize(file.size());
+ if (file.read(data.data(), data.size()) != data.size())
+ error("Truncated 3D Construction Kit Atari ST data");
+ file.close();
+
+ // Standalone runners XOR each word with their export key.
+ const byte key[] = {byte(data[0] ^ 'A'), byte(data[1] ^ 'M')};
+ for (uint i = 0; i < data.size(); i++)
+ data[i] ^= key[i & 1];
+ Common::MemoryReadStream world(data.data(), data.size());
+ loadWorld(world);
+
+ world.seek(world.size() - 8);
+ uint32 paletteOffset = world.readUint32BE();
+ uint32 paletteEnd = world.size() - 12;
+ if (paletteOffset < 500 || paletteOffset > paletteEnd ||
+ paletteEnd - paletteOffset != 34 * _areaData.size())
+ error("Invalid 3D Construction Kit Atari ST palette table");
+ Common::HashMap<uint16, bool> loadedPalettes;
+ world.seek(paletteOffset);
+ while (world.pos() < paletteEnd) {
+ uint16 area = world.readUint16BE();
+ if (!_areaData.contains(area) || loadedPalettes.contains(area))
+ error("Invalid 3D Construction Kit Atari ST palette area %u", area);
+ loadPaletteAtari(world, _areaData[area].palette);
+ loadedPalettes[area] = true;
+ }
+
+ if (!file.open(_gameDescription->filesDescriptions[1].fileName))
+ error("Unable to open 3D Construction Kit Atari ST border");
+ if (file.size() != 32128)
+ error("Invalid 3D Construction Kit Atari ST border size");
+ file.seek(4);
+ loadPaletteAtari(file, _palette);
+ file.seek(0);
+ Image::NeoDecoder decoder;
+ if (!decoder.loadStream(file) || file.err())
+ error("Unable to decode 3D Construction Kit Atari ST border");
+ _borderPixels.copyFrom(*decoder.getSurface());
+ _border = new Graphics::ManagedSurface();
+ _border->copyFrom(*decoder.getSurface());
+ _border->convertToInPlace(_gfx->_texturePixelFormat, _palette, 16);
+}
+
+void KitEngine::updateBorderAtari() {
+ // Palette changes apply to whole scanlines, including the side panels.
+ Graphics::Surface band = _borderPixels.getSubArea(Common::Rect(0, _viewArea.top, _screenW, _viewArea.bottom));
+ Graphics::Surface *colored = band.convertTo(_gfx->_texturePixelFormat, _gfx->_palette, 16);
+ _border->blitFrom(*colored, Common::Point(0, _viewArea.top));
+ colored->free();
+ delete colored;
+ processBorder();
+}
+
+void KitEngine::splitColorAtari(byte &color, byte &extraColor) {
+ extraColor = color & 0xf;
+ color >>= 4;
+ // A zero extra colour means solid fill to the renderer.
+ if (!extraColor)
+ SWAP(color, extraColor);
+}
+
+void KitEngine::drawBackground() {
+ if (!isAtariST()) {
+ FreescapeEngine::drawBackground();
+ return;
+ }
+ clearBackground();
+ byte first = _currentArea->_skyColor;
+ byte second = _areaData[_currentArea->getAreaID()].skyExtraColor;
+ byte r1, g1, b1, r2, g2, b2;
+ _gfx->readFromPalette(first, r1, g1, b1);
+ _gfx->readFromPalette(second, r2, g2, b2);
+ _gfx->clear(r1, g1, b1);
+ if (first != second)
+ _gfx->fillViewportStippled(r1, g1, b1, r2, g2, b2, _gfx->_defaultStippleArray);
+}
+
+} // namespace Freescape
diff --git a/engines/freescape/games/3dck/ui.cpp b/engines/freescape/games/3dck/ui.cpp
index 5f757430745..52efd539943 100644
--- a/engines/freescape/games/3dck/ui.cpp
+++ b/engines/freescape/games/3dck/ui.cpp
@@ -26,9 +26,12 @@
namespace Freescape {
-uint32 KitEngine::indicatorColor(byte color) const {
+uint32 KitEngine::indicatorColor(byte color, int y) const {
+ const byte *palette = _palette;
+ if (_renderMode == Common::kRenderAtariST && y >= _viewArea.top && y < _viewArea.bottom)
+ palette = _gfx->_palette;
return _scriptSurface.format.ARGBToColor(255,
- _palette[3 * color], _palette[3 * color + 1], _palette[3 * color + 2]);
+ palette[3 * color], palette[3 * color + 1], palette[3 * color + 2]);
}
void KitEngine::printMessage(uint16 indicator, const Common::String &message) {
@@ -59,8 +62,8 @@ void KitEngine::printMessage(uint16 indicator, const Common::String &message) {
if (y >= surface.h)
break;
Common::Rect cell(x, y, MIN(x + 8, int(surface.w)), MIN(y + 8, int(surface.h)));
- surface.fillRect(cell, indicatorColor(data[11]));
- font.drawChar(&surface, chr, x, y, indicatorColor(data[10]));
+ surface.fillRect(cell, indicatorColor(data[11], rect.top + y));
+ font.drawChar(&surface, chr, x, y, indicatorColor(data[10], rect.top + y));
x += 8;
if (x >= surface.w) {
x = 0;
@@ -82,7 +85,7 @@ void KitEngine::updateIndicators() {
int32 first = int32((uint32(data[5]) << 16) | data[6]);
int32 last = int32((uint32(data[7]) << 16) | data[8]);
int32 value = CLIP<int32>(_kitVariables[data[9] & 0xff], MIN(first, last), MAX(first, last));
- uint32 foreground = indicatorColor(data[10]), background = indicatorColor(data[11]);
+ uint32 foreground = indicatorColor(data[10], rect.top), background = indicatorColor(data[11], rect.top);
_scriptSurface.fillRect(rect, background);
if (data[0] == 2) {
int digits = MIN<int>(rect.width() / 8, 8);
diff --git a/engines/freescape/gfx_opengl_shaders.cpp b/engines/freescape/gfx_opengl_shaders.cpp
index a7606b1cb88..8860769b27d 100644
--- a/engines/freescape/gfx_opengl_shaders.cpp
+++ b/engines/freescape/gfx_opengl_shaders.cpp
@@ -908,10 +908,8 @@ void OpenGLShaderRenderer::fillViewportStippled(uint8 r1, uint8 g1, uint8 b1, ui
glBindBuffer(GL_ARRAY_BUFFER, _triangleVBO);
glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(float), _verts, GL_DYNAMIC_DRAW);
- glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- glDisableVertexAttribArray(0);
useStipple(false);
glDepthMask(GL_TRUE);
diff --git a/engines/freescape/language/detokeniser.h b/engines/freescape/language/detokeniser.h
index b31e54cd732..6afbeebdb22 100644
--- a/engines/freescape/language/detokeniser.h
+++ b/engines/freescape/language/detokeniser.h
@@ -29,7 +29,7 @@ namespace Freescape {
// Classic games retain their byte-oriented dialect on DOS, Amiga and Atari ST.
Common::String detokeniseFreescapeCondition(const Common::Array<uint16> &tokenisedCondition, FCLInstructionVector &instructions, bool isAmigaAtari);
Common::String detokeniseKit8Condition(const Common::Array<byte> &tokenisedCondition, FCLInstructionVector &instructions);
-Common::String detokeniseKit16Condition(const Common::Array<byte> &tokenisedCondition, FCLInstructionVector &instructions);
+Common::String detokeniseKit16Condition(const Common::Array<byte> &tokenisedCondition, FCLInstructionVector &instructions, bool isAmigaAtari);
void normaliseKitOperands(FCLInstruction &instruction);
diff --git a/engines/freescape/language/detokeniser_3dck16.cpp b/engines/freescape/language/detokeniser_3dck16.cpp
index dfae0771a4b..9c9a19daac6 100644
--- a/engines/freescape/language/detokeniser_3dck16.cpp
+++ b/engines/freescape/language/detokeniser_3dck16.cpp
@@ -83,18 +83,25 @@ static const FCLOpcode kKitOpcodes[] = {
{0xff, Token::END, "END", 0, 0, 0}
};
-Common::String detokeniseKit16Condition(const Common::Array<byte> &tokenisedCondition, FCLInstructionVector &instructions) {
+Common::String detokeniseKit16Condition(const Common::Array<byte> &tokenisedCondition, FCLInstructionVector &instructions, bool isAmigaAtari) {
Common::String detokenisedStream;
int loops = 0;
for (uint32 bytePointer = 0; bytePointer < tokenisedCondition.size();) {
if (tokenisedCondition.size() - bytePointer < 2)
error("Truncated 16-bit FCL instruction at %u", bytePointer);
byte count = tokenisedCondition[bytePointer], opcode = tokenisedCondition[bytePointer + 1];
- if (tokenisedCondition.size() - bytePointer < 2U + 2U * count)
- error("Truncated 16-bit FCL instruction at %u", bytePointer);
const FCLOpcode *entry = findFCLOpcode(kKitOpcodes, opcode);
+ if (!entry && isAmigaAtari) {
+ // The Atari runner aborts this condition when the command is reached.
+ FCLInstruction instruction(Token::UNKNOWN);
+ instruction.setSource(opcode, Token::CONSTANT);
+ instructions.push_back(instruction);
+ return detokenisedStream + Common::String::format("UNKNOWN (%02x)\n", opcode);
+ }
if (!entry)
error("Unknown 16-bit FCL opcode %02x at %u", opcode, bytePointer);
+ if (tokenisedCondition.size() - bytePointer < 2U + 2U * count)
+ error("Truncated 16-bit FCL instruction at %u", bytePointer);
if (count < entry->minArgs || count > entry->maxArgs)
error("Invalid argument count for 16-bit FCL opcode %02x at %u", opcode, bytePointer);
diff --git a/engines/freescape/language/execution_3dck16.cpp b/engines/freescape/language/execution_3dck16.cpp
index f0e12ce7dcd..486be263ae6 100644
--- a/engines/freescape/language/execution_3dck16.cpp
+++ b/engines/freescape/language/execution_3dck16.cpp
@@ -319,6 +319,10 @@ FCLExecutionResult KitEngine::executeCode(ScriptState &script, uint &budget) {
uint32 ip = script.ip++;
const FCLInstruction &instruction = (*script.code)[ip];
switch (instruction.getType()) {
+ case Token::UNKNOWN:
+ warning("Unknown 16-bit FCL opcode %02x in area %u, object %u; stopping condition",
+ instruction._source, script.area, script.object ? script.object->id : 0);
+ return kFCLFinished;
case Token::NOP:
case Token::ENDIF:
break;
diff --git a/engines/freescape/module.mk b/engines/freescape/module.mk
index ef50be2b776..1b9eba6f068 100644
--- a/engines/freescape/module.mk
+++ b/engines/freescape/module.mk
@@ -55,6 +55,7 @@ MODULE_OBJS := \
games/3dck/3dck.o \
games/3dck/8bit.o \
games/3dck/8bitUI.o \
+ games/3dck/atari.o \
games/3dck/c64.o \
games/3dck/ui.o \
games/3dck/zx.o \
Commit: 4ccc9ad0540f197ab98648df5061946b7a403768
https://github.com/scummvm/scummvm/commit/4ccc9ad0540f197ab98648df5061946b7a403768
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-12T12:21:36+02:00
Commit Message:
FREESCAPE: added Egyptman for atari
Changed paths:
engines/freescape/detection.cpp
engines/freescape/games/3dck/3dck.h
engines/freescape/games/3dck/ui.cpp
diff --git a/engines/freescape/detection.cpp b/engines/freescape/detection.cpp
index 75a8fe63c0e..04a8553c400 100644
--- a/engines/freescape/detection.cpp
+++ b/engines/freescape/detection.cpp
@@ -1355,6 +1355,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSUPPORTED,
GUIO1(GUIO_NOMIDI)
},
+ {
+ "3dkit",
+ "Egyptman v2.3 (Shareware)",
+ AD_ENTRY1s("EGYPT.RUN", "548472dcf3fd012f3a901a38476f7444", 97634),
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO2(GUIO_NOMIDI, GUIO_RENDERVGA)
+ },
{
"3dkit",
"Funnyrace",
diff --git a/engines/freescape/games/3dck/3dck.h b/engines/freescape/games/3dck/3dck.h
index 53f3c021c59..8bfba2e3e69 100644
--- a/engines/freescape/games/3dck/3dck.h
+++ b/engines/freescape/games/3dck/3dck.h
@@ -31,6 +31,7 @@ class KitEngine : public FreescapeEngine {
public:
KitEngine(OSystem *syst, const ADGameDescription *gd);
+ void initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) override;
void loadAssets() override;
void initGameState() override;
void gotoArea(uint16 areaID, int entranceID) override;
diff --git a/engines/freescape/games/3dck/ui.cpp b/engines/freescape/games/3dck/ui.cpp
index 52efd539943..f346cfecf04 100644
--- a/engines/freescape/games/3dck/ui.cpp
+++ b/engines/freescape/games/3dck/ui.cpp
@@ -19,6 +19,9 @@
*
*/
+#include "backends/keymapper/action.h"
+#include "backends/keymapper/keymap.h"
+#include "common/translation.h"
#include "graphics/fonts/dosfont.h"
#include "math/utils.h"
@@ -26,6 +29,14 @@
namespace Freescape {
+void KitEngine::initKeymaps(Common::Keymap *engineKeyMap, Common::Keymap *infoScreenKeyMap, const char *target) {
+ FreescapeEngine::initKeymaps(engineKeyMap, infoScreenKeyMap, target);
+ Common::Action *act = new Common::Action("ACTIVATE", _("Activate"));
+ act->setCustomEngineActionEvent(kActionActivate);
+ act->addDefaultInputMapping(_useWASDControls ? "e" : "a");
+ engineKeyMap->addAction(act);
+}
+
uint32 KitEngine::indicatorColor(byte color, int y) const {
const byte *palette = _palette;
if (_renderMode == Common::kRenderAtariST && y >= _viewArea.top && y < _viewArea.bottom)
Commit: cebd12bbf1c6ca41e43343992edebcc9654157ab
https://github.com/scummvm/scummvm/commit/cebd12bbf1c6ca41e43343992edebcc9654157ab
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-12T12:21:36+02:00
Commit Message:
SCUMM: RA2: potential fix for L2 enemy partial decoding
Changed paths:
engines/scumm/smush/rebel/codec_ra2.cpp
engines/scumm/smush/rebel/codec_ra2.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
diff --git a/engines/scumm/smush/rebel/codec_ra2.cpp b/engines/scumm/smush/rebel/codec_ra2.cpp
index a8681773fd4..9e92fd52702 100644
--- a/engines/scumm/smush/rebel/codec_ra2.cpp
+++ b/engines/scumm/smush/rebel/codec_ra2.cpp
@@ -27,30 +27,31 @@
namespace Scumm {
-static void bompDecodeLineOpaqueBounded(byte *dst, const byte *src, const byte *srcEnd, int len) {
+static void bompDecodeLineBounded(byte *dst, const byte *src, const byte *srcEnd, int len, int sourceSkipX, bool opaque) {
while (len > 0 && src < srcEnd) {
byte code = *src++;
int num = (code >> 1) + 1;
- if (num > len)
- num = len;
+ int skip = MIN(num, sourceSkipX);
+ sourceSkipX -= skip;
if (code & 1) {
if (src >= srcEnd)
break;
- memset(dst, *src++, num);
- dst += num;
- len -= num;
+ byte color = *src++;
+ num = MIN(num - skip, len);
+ if (opaque || color != 0)
+ memset(dst, color, num);
} else {
- int toCopy = num;
- if (toCopy > (int)(srcEnd - src))
- toCopy = (int)(srcEnd - src);
- memcpy(dst, src, toCopy);
- src += toCopy;
- dst += toCopy;
- len -= toCopy;
- if (toCopy < num)
+ num = MIN(num, (int)(srcEnd - src));
+ if (skip > num)
break;
+ src += skip;
+ num = MIN(num - skip, len);
+ memcpy(dst, src, num);
+ src += num;
}
+ dst += num;
+ len -= num;
}
}
@@ -187,6 +188,10 @@ void copyRA2Handler7PerspectiveViewport(byte *dst, int dstPitch, int dstWidth, i
// RLE decoder for opaque backgrounds, including color 0.
void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
+ smushDecodeRA2RLE(dst, src, left, top, width, height, pitch, dataSize, 0, true);
+}
+
+void smushDecodeRA2RLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, int sourceSkipX, bool opaque) {
if (dataSize <= 0)
return;
@@ -200,7 +205,7 @@ void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int wid
const byte *lineEnd = src + lineSize;
dst += left;
- bompDecodeLineOpaqueBounded(dst, src, lineEnd, width);
+ bompDecodeLineBounded(dst, src, lineEnd, width, sourceSkipX, opaque);
src = lineEnd;
dst += pitch - left;
}
diff --git a/engines/scumm/smush/rebel/codec_ra2.h b/engines/scumm/smush/rebel/codec_ra2.h
index 99428f2f514..f65aceda07f 100644
--- a/engines/scumm/smush/rebel/codec_ra2.h
+++ b/engines/scumm/smush/rebel/codec_ra2.h
@@ -27,6 +27,7 @@
namespace Scumm {
void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
+void smushDecodeRA2RLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, int sourceSkipX, bool opaque);
void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeRA2SkipRemap(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, const byte *remap, byte addColor);
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index e42e08cd969..bbc7c102270 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -1388,7 +1388,8 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
}
if (codec == SMUSH_CODEC_LINE_UPDATE || codec == SMUSH_CODEC_LINE_UPDATE2 ||
- codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_UNCOMPRESSED) {
+ codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_UNCOMPRESSED ||
+ (_ra2FrameSourceSkipX > 0 && (codec == SMUSH_CODEC_RLE || codec == SMUSH_CODEC_RLE_ALT))) {
_ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
@@ -1406,6 +1407,13 @@ bool SmushPlayerRebel2::handleGameCodecDecode(int codec, const uint8 *src, int l
if (isRebel2FullFrameDeltaCodec(codec))
return ra2DecodePlacedDeltaCodec(codec, src, left, top, width, height, pitch, dataSize);
+ if (_ra2FrameSourceSkipX > 0 && (codec == SMUSH_CODEC_RLE || codec == SMUSH_CODEC_RLE_ALT)) {
+ const bool opaque = codec == SMUSH_CODEC_RLE_ALT || (_curVideoFlags & 0x100) != 0;
+ src = smushSkipRLELines(src, dataSize, _ra2FrameSourceSkipY);
+ smushDecodeRA2RLE(_dst, src, left, top, width, height, pitch, dataSize, _ra2FrameSourceSkipX, opaque);
+ return true;
+ }
+
if (codec == SMUSH_CODEC_SKIP_RLE && parm2 >= 0x100) {
if (parm2 == 0x100 && dataSize >= 256) {
memcpy(_ra2SkipRemapTable, src, 256);
Commit: e973f7e7fb662383d8fb41332d6047feff4c8cf4
https://github.com/scummvm/scummvm/commit/e973f7e7fb662383d8fb41332d6047feff4c8cf4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-12T13:24:06+02:00
Commit Message:
SCUMM: RA2: added missing blur effect affecting at least L2
Changed paths:
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/codec_ra2.h
engines/scumm/smush/rebel/font_rebel2.cpp
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 77a3edc8560..656ad55fc71 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -3515,9 +3515,12 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
int overlayDrawX = renderHiRes ? (nativeOverlayX - nativeViewX) * renderScale : nativeOverlayX;
int overlayDrawY = renderHiRes ? (nativeOverlayY - nativeViewY) * renderScale : nativeOverlayY;
- renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
- 0, 0, width, renderHeight,
- overlayDrawX, overlayDrawY, _grd005Sprite, overlayIdx, false, renderScale, false);
+ if (!drawRebel2Codec45Sprite(_grd005Sprite, renderBitmap, pitch, width, height,
+ Common::Rect(0, 0, width, renderHeight), overlayDrawX, overlayDrawY, overlayIdx, renderScale)) {
+ renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
+ 0, 0, width, renderHeight,
+ overlayDrawX, overlayDrawY, _grd005Sprite, overlayIdx, false, renderScale, false);
+ }
debugC(DEBUG_INSANE, "Handler25 PRE: GRD005 at (%d,%d) nutOff(%d,%d) viewOff(%d,%d) size(%d,%d) mode=%d scale=%d",
overlayDrawX, overlayDrawY, overlayXOffset, overlayYOffset,
diff --git a/engines/scumm/smush/rebel/codec_ra2.h b/engines/scumm/smush/rebel/codec_ra2.h
index f65aceda07f..972fc90e7c0 100644
--- a/engines/scumm/smush/rebel/codec_ra2.h
+++ b/engines/scumm/smush/rebel/codec_ra2.h
@@ -31,6 +31,7 @@ void smushDecodeRA2RLE(byte *dst, const byte *src, int left, int top, int width,
void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeRA2SkipRemap(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize, const byte *remap, byte addColor);
+bool smushPrepareRA2BlurData(const byte *src, int dataSize, byte *palette, byte *lookup, const byte *&maskData, int &maskSize);
void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup);
void smushDecodeRA2BlurClip(byte *dst, const byte *src, int left, int top,
int clipLeft, int clipTop, int clipRight, int clipBottom,
diff --git a/engines/scumm/smush/rebel/font_rebel2.cpp b/engines/scumm/smush/rebel/font_rebel2.cpp
index 7b903401e63..ceb8e23b885 100644
--- a/engines/scumm/smush/rebel/font_rebel2.cpp
+++ b/engines/scumm/smush/rebel/font_rebel2.cpp
@@ -547,12 +547,20 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
memcpy(_codec23Lookup, frame.data, sizeof(_codec23Lookup));
}
+ // Codec 45 setup frames have no drawable dimensions.
+ if (frame.codec == 45 && ((int16)frame.width < 0 || (int16)frame.height < 0)) {
+ frame.width = 0;
+ frame.height = 0;
+ }
+
const uint64 pixels = (uint64)frame.width * frame.height;
if (pixels == 0) {
frame.width = 0;
frame.height = 0;
- frame.data = nullptr;
- frame.dataSize = 0;
+ if (frame.codec != 45) {
+ frame.data = nullptr;
+ frame.dataSize = 0;
+ }
} else if (pixels > kRebel2MaxSpritePixels || decodedLength + pixels > kRebel2MaxDecodedSpriteBytes) {
warning("Rebel2NutRenderer::loadRebel2SpriteFromData: invalid sprite dimensions %ux%u at frame %d",
frame.width, frame.height, frameCount);
@@ -596,6 +604,12 @@ void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSiz
_chars[i].height = frame.height;
_chars[i].transparency = kDefaultTransparentColor;
+ if (frame.codec == 45 && frame.data) {
+ const byte *maskData;
+ int maskSize;
+ smushPrepareRA2BlurData(frame.data, frame.dataSize, _codec45Palette, _codec45Lookup, maskData, maskSize);
+ }
+
if (frame.width == 0 || frame.height == 0 || frame.data == nullptr)
continue;
Commit: 3474234780145c5014bfd9dedcdd44f232a6ed41
https://github.com/scummvm/scummvm/commit/3474234780145c5014bfd9dedcdd44f232a6ed41
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-12T13:54:23+02:00
Commit Message:
SCUMM: RA2: properly implement pause menu including film effect
Changed paths:
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
engines/scumm/smush/smush_player.h
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index a4d1a537894..b20bccd4d8a 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -23,6 +23,7 @@
#include "common/system.h"
#include "common/events.h"
#include "common/util.h"
+#include "graphics/blit.h"
#include "graphics/paletteman.h"
#include "audio/mixer.h"
@@ -368,78 +369,110 @@ void InsaneRebel2::drawMenuOverlay(byte *renderBitmap, int pitch, int width, int
drawMenuItems(renderBitmap, pitch, width, height, menuItems, 7, _menuSelection);
}
-void pauseFillRect(byte *buf, int bufW, int bufH, int x, int y, int w, int h, byte color) {
- if (x < 0) { w += x; x = 0; }
- if (y < 0) { h += y; y = 0; }
- if (x + w > bufW) w = bufW - x;
- if (y + h > bufH) h = bufH - y;
- if (w <= 0 || h <= 0) return;
- for (int row = y; row < y + h; row++)
- memset(buf + row * bufW + x, color, w);
+byte blendRebel2PausePixels(byte a, byte b) {
+ if (a > b)
+ SWAP(a, b);
+ if (a >= 16 && b < 80)
+ return (a + b) / 2;
+ if (b == 0 || b == 4 || (b >= 16 && b < 80))
+ return b;
+ return a;
+}
+
+void scaleRebel2PauseBackground(const Graphics::Surface &src, Graphics::Surface &dst) {
+ dst.create(src.w * 2, src.h * 2, src.format);
+ for (int y = 0; y < src.h; y++) {
+ const byte *srcRow = (const byte *)src.getBasePtr(0, y);
+ byte *dstRow = (byte *)dst.getBasePtr(0, y * 2);
+ for (int x = 0; x < src.w; x++) {
+ dstRow[x * 2] = srcRow[x];
+ const byte nextColor = x + 1 < src.w ? srcRow[x + 1] : 0;
+ dstRow[x * 2 + 1] = blendRebel2PausePixels(srcRow[x], nextColor);
+ }
+ }
+ for (int y = 1; y < dst.h - 1; y += 2) {
+ byte *row = (byte *)dst.getBasePtr(0, y);
+ for (int x = 0; x < dst.w; x++)
+ row[x] = blendRebel2PausePixels(row[x - dst.pitch], row[x + dst.pitch]);
+ }
+ memcpy(dst.getBasePtr(0, dst.h - 1), dst.getBasePtr(0, dst.h - 2), dst.w);
}
void InsaneRebel2::showPauseOverlay() {
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
- if (!splayer)
+ if (!splayer || _pauseOverlayActive)
return;
- byte *frameBuffer = splayer->_dst;
- byte *palette = splayer->_pal;
- int width = splayer->_width;
- int height = splayer->_height;
-
- if (!frameBuffer || !palette || width <= 0 || height <= 0)
+ // Capture the visible viewport without modifying the video decoder's buffers.
+ Graphics::Surface *screen = _vm->_system->lockScreen();
+ if (!screen)
return;
-
- int screenW = MIN(width, (int)_vm->_screenWidth);
- int screenH = MIN(height, (int)_vm->_screenHeight);
-
- memcpy(_savedPausePalette, palette, 768);
+ _savedPauseScreen.copyFrom(*screen);
+ _vm->_system->unlockScreen();
+ _vm->_system->getPaletteManager()->grabPalette(_savedPausePalette, 0, 256);
_pauseOverlayActive = true;
- byte dimPal[768];
- memcpy(dimPal, palette, 768);
- for (int i = 0; i < 768; i++)
- dimPal[i] >>= 2;
-
- dimPal[0x50 * 3 + 0] = 80; dimPal[0x50 * 3 + 1] = 80; dimPal[0x50 * 3 + 2] = 80;
- dimPal[0x51 * 3 + 0] = 110; dimPal[0x51 * 3 + 1] = 110; dimPal[0x51 * 3 + 2] = 110;
- dimPal[1 * 3 + 0] = 255; dimPal[1 * 3 + 1] = 255; dimPal[1 * 3 + 2] = 255;
- dimPal[2 * 3 + 0] = 188; dimPal[2 * 3 + 1] = 188; dimPal[2 * 3 + 2] = 188;
- dimPal[3 * 3 + 0] = 128; dimPal[3 * 3 + 1] = 128; dimPal[3 * 3 + 2] = 128;
- dimPal[4 * 3 + 0] = 0; dimPal[4 * 3 + 1] = 0; dimPal[4 * 3 + 2] = 0;
- dimPal[5 * 3 + 0] = 252; dimPal[5 * 3 + 1] = 252; dimPal[5 * 3 + 2] = 252;
-
- _vm->_system->getPaletteManager()->setPalette(dimPal, 0, 256);
-
- pauseFillRect(frameBuffer, width, height, 0, 0x17, 0x140, 2, 0x50);
- pauseFillRect(frameBuffer, width, height, 0, 0xAF, 0x140, 2, 0x50);
-
- pauseFillRect(frameBuffer, width, height, 0, 0, 0x28, 200, 0x50);
- pauseFillRect(frameBuffer, width, height, 0x118, 0, 0x28, 200, 0x50);
-
- for (int i = 0; i < 6; i++) {
- int yOff = i * 0x24; // i * 36
- pauseFillRect(frameBuffer, width, height, 0x0C, yOff, 0x19, 0x11, 0x51);
- pauseFillRect(frameBuffer, width, height, 0x0B, yOff + 1, 0x1B, 0x0F, 0x51);
- pauseFillRect(frameBuffer, width, height, 0x0D, yOff, 0x17, 0x11, 4);
- pauseFillRect(frameBuffer, width, height, 0x0B, yOff + 2, 0x1B, 0x0D, 4);
- pauseFillRect(frameBuffer, width, height, 0x0C, yOff + 1, 0x19, 0x0F, 4);
+ Graphics::Surface background;
+ background.create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
+ byte *pixels = (byte *)background.getPixels();
+ Graphics::scaleBlit(pixels, (const byte *)_savedPauseScreen.getPixels(),
+ background.pitch, _savedPauseScreen.pitch, background.w, background.h,
+ _savedPauseScreen.w, _savedPauseScreen.h, background.format);
+
+ // The original uses a 64-shade tint, then repeats the neighbouring film frames.
+ byte previousColor = pixels[0];
+ for (int i = 0; i < background.w * background.h; i++) {
+ const byte color = pixels[i];
+ pixels[i] = ((_savedPausePalette[previousColor * 3 + 1] + _savedPausePalette[color * 3 + 1]) >> 3) + 16;
+ previousColor = color;
+ }
+ memcpy(background.getBasePtr(0, 0), background.getBasePtr(0, 150), 25 * background.pitch);
+ memcpy(background.getBasePtr(0, 175), background.getBasePtr(0, 25), 25 * background.pitch);
+
+ background.fillRect(Common::Rect(0, 23, 320, 25), 80);
+ background.fillRect(Common::Rect(0, 175, 320, 177), 80);
+ background.fillRect(Common::Rect(0, 0, 40, 200), 80);
+ background.fillRect(Common::Rect(280, 0, 320, 200), 80);
+ for (int side = 0; side < 2; side++) {
+ const int x = side ? 282 : 11;
+ for (int i = 0; i < 6; i++) {
+ const int y = i * 36;
+ background.fillRect(Common::Rect(x + 1, y, x + 26, y + 17), 81);
+ background.fillRect(Common::Rect(x, y + 1, x + 27, y + 16), 81);
+ background.fillRect(Common::Rect(x + 2, y, x + 25, y + 17), 4);
+ background.fillRect(Common::Rect(x, y + 2, x + 27, y + 15), 4);
+ background.fillRect(Common::Rect(x + 1, y + 1, x + 26, y + 16), 4);
+ }
}
- for (int i = 0; i < 6; i++) {
- int yOff = i * 0x24;
- int xBase = 0x11A;
- pauseFillRect(frameBuffer, width, height, xBase, yOff, 0x19, 0x11, 0x51);
- pauseFillRect(frameBuffer, width, height, xBase - 1, yOff + 1, 0x1B, 0x0F, 0x51);
- pauseFillRect(frameBuffer, width, height, xBase + 1, yOff, 0x17, 0x11, 4);
- pauseFillRect(frameBuffer, width, height, xBase - 1, yOff + 2, 0x1B, 0x0D, 4);
- pauseFillRect(frameBuffer, width, height, xBase, yOff + 1, 0x19, 0x0F, 4);
+ byte pausePalette[768];
+ memcpy(pausePalette, _savedPausePalette, sizeof(pausePalette));
+ const byte textColors[] = {
+ 0, 0, 0, 255, 255, 255, 188, 188, 188, 128, 128, 128, 0, 0, 0,
+ 0, 255, 0, 0, 199, 0, 0, 147, 0, 0, 95, 0
+ };
+ memcpy(pausePalette, textColors, sizeof(textColors));
+ for (int i = 0; i < 64; i++) {
+ // Original HLS palette: hue 10, lightness 2*i, saturation 78.
+ const int m1 = 354 * i;
+ const int m2 = 666 * i;
+ pausePalette[(16 + i) * 3] = m2 / 255;
+ pausePalette[(16 + i) * 3 + 1] = (m1 + (m2 - m1) * 60 / 255) / 255;
+ pausePalette[(16 + i) * 3 + 2] = m1 / 255;
}
+ const byte filmColors[] = {65, 36, 39, 33, 18, 29, 49, 27, 34, 65, 36, 39};
+ memcpy(pausePalette + 80 * 3, filmColors, sizeof(filmColors));
+
+ Graphics::Surface overlay;
+ if (isHiRes())
+ scaleRebel2PauseBackground(background, overlay);
+ else
+ overlay.copyFrom(background);
+ background.free();
const char *pauseText = splayer->getString(0x78);
if (!pauseText || !pauseText[0])
- pauseText = "Game Paused";
+ pauseText = "^f01^c005Game Paused";
SmushMultiFont *multiFont = splayer->getMultiFont();
if (!multiFont) {
@@ -447,12 +480,27 @@ void InsaneRebel2::showPauseOverlay() {
multiFont = splayer->getMultiFont();
}
if (multiFont) {
- Common::Rect clipRect(0, 0, screenW, screenH);
- multiFont->drawString(pauseText, frameBuffer, clipRect, 10, 10, width, 4, kStyleAlignLeft);
+ Common::Rect clipRect(overlay.w, overlay.h);
+ multiFont->drawString(pauseText, (byte *)overlay.getPixels(), clipRect,
+ 10, isHiRes() ? 20 : 10, overlay.pitch, 1, kStyleAlignLeft);
}
- _vm->_system->copyRectToScreen(frameBuffer, width, 0, 0, screenW, screenH);
+ _vm->_system->getPaletteManager()->setPalette(pausePalette, 0, 256);
+ _vm->_system->copyRectToScreen(overlay.getPixels(), overlay.pitch, 0, 0, overlay.w, overlay.h);
+ _vm->_system->updateScreen();
+ overlay.free();
+}
+
+void InsaneRebel2::hidePauseOverlay() {
+ if (!_pauseOverlayActive)
+ return;
+
+ _vm->_system->getPaletteManager()->setPalette(_savedPausePalette, 0, 256);
+ _vm->_system->copyRectToScreen(_savedPauseScreen.getPixels(), _savedPauseScreen.pitch,
+ 0, 0, _savedPauseScreen.w, _savedPauseScreen.h);
_vm->_system->updateScreen();
+ _savedPauseScreen.free();
+ _pauseOverlayActive = false;
}
int InsaneRebel2::runMainMenu() {
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index d449a8fd3da..9761d3aa758 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -592,6 +592,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
InsaneRebel2::~InsaneRebel2() {
restoreIOSGamepadController();
setVirtualKeyboardVisible(false);
+ _savedPauseScreen.free();
_vm->_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
@@ -638,10 +639,7 @@ void InsaneRebel2::openGameplayMainMenu(SmushPlayer *splayer) {
if (!splayer)
return;
- if (_pauseOverlayActive) {
- _vm->_system->getPaletteManager()->setPalette(_savedPausePalette, 0, 256);
- _pauseOverlayActive = false;
- }
+ hidePauseOverlay();
if (!splayer->_paused)
splayer->pause();
@@ -966,10 +964,7 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (pressed && splayer && splayer->_paused && _gameState == kStateGameplay) {
debugC(DEBUG_INSANE, "Joystick action while paused - unpausing");
- if (_pauseOverlayActive) {
- _vm->_system->getPaletteManager()->setPalette(_savedPausePalette, 0, 256);
- _pauseOverlayActive = false;
- }
+ hidePauseOverlay();
splayer->unpause();
return true;
}
@@ -1095,10 +1090,7 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (splayer && splayer->_paused && _gameState == kStateGameplay) {
debugC(DEBUG_INSANE, "Key pressed while paused - unpausing");
- if (_pauseOverlayActive) {
- _vm->_system->getPaletteManager()->setPalette(_savedPausePalette, 0, 256);
- _pauseOverlayActive = false;
- }
+ hidePauseOverlay();
splayer->unpause();
if (event.kbd.keycode == Common::KEYCODE_ESCAPE && _rebelHandler != 0) {
debugC(DEBUG_INSANE, "ESC during pause - opening global menu");
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index ecaec832099..880451c70a8 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -40,6 +40,8 @@
#include "audio/audiostream.h"
#include "audio/mixer.h"
+#include "graphics/surface.h"
+
namespace Audio {
class QueuingAudioStream;
class SoundHandle;
@@ -388,6 +390,7 @@ public:
NutRenderer *_smush_povfontNut;
byte _savedPausePalette[768];
+ Graphics::Surface _savedPauseScreen;
bool _pauseOverlayActive;
bool _introCursorPushed;
@@ -918,6 +921,7 @@ public:
void renderScoreHUD(byte *renderBitmap, int pitch, int width, int height, int statusBarY);
void showPauseOverlay();
+ void hidePauseOverlay();
int _targetLockTimer;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index bbc7c102270..a346b4896ce 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -131,6 +131,13 @@ SmushPlayerRebel2::~SmushPlayerRebel2() {
destroyGamePlayerFields();
}
+void SmushPlayerRebel2::unpause() {
+ // A modal dialog or focus change must not dismiss the in-game pause.
+ if (_insane && static_cast<InsaneRebel2 *>(_insane)->_pauseOverlayActive)
+ return;
+ SmushPlayer::unpause();
+}
+
void SmushPlayerRebel2::initGamePlayerFields() {
_multiFont = nullptr;
_storedFobjData = nullptr;
@@ -244,6 +251,10 @@ void SmushPlayerRebel2::initGameVideoState() {
}
void SmushPlayerRebel2::releaseGameVideoState() {
+ if (_insane && static_cast<InsaneRebel2 *>(_insane)->_pauseOverlayActive) {
+ static_cast<InsaneRebel2 *>(_insane)->hidePauseOverlay();
+ unpause();
+ }
delete _loadContinuationStream;
_loadContinuationStream = nullptr;
_loadPlaybackPending = false;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 9326c8e8210..8543a8041e3 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -30,6 +30,7 @@ class SmushPlayerRebel2 : public SmushPlayer {
public:
SmushPlayerRebel2(ScummEngine_v7 *scumm, IMuseDigital *imuseDigital, Insane *insane);
~SmushPlayerRebel2() override;
+ void unpause() override;
bool ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY);
bool ra2PromoteHandler7PerspectiveToHiRes(int perspectiveX, int perspectiveY, int viewShift);
bool isPlayingLoadBuffer() const { return _loadContinuationStream != nullptr; }
diff --git a/engines/scumm/smush/smush_player.h b/engines/scumm/smush/smush_player.h
index e4ee345163b..0889ae373fa 100644
--- a/engines/scumm/smush/smush_player.h
+++ b/engines/scumm/smush/smush_player.h
@@ -258,7 +258,7 @@ public:
virtual ~SmushPlayer();
void pause();
- void unpause();
+ virtual void unpause();
void play(const char *filename, int32 speed, int32 offset = 0, int32 startFrame = 0);
void release();
More information about the Scummvm-git-logs
mailing list