[Scummvm-git-logs] scummvm master -> e2d16ffcecbf7e7d6fb25a6156ca1155a7605355
neuromancer
noreply at scummvm.org
Sun Mar 29 13:28:28 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:
3ef9c65c75 FREESCAPE: make the endgame for driller more stable
476425a3d3 FREESCAPE: avoid heartbeat to beat too fast
b300be71a3 FREESCAPE: loading of cpc images directly from data in dark cpc
167355fb19 FREESCAPE: wait before restoring ECD in dark
43ca847672 FREESCAPE: correctly render stipple patterns in CPC
e2d16ffcec FREESCAPE: better rendering from driller/castle cpc
Commit: 3ef9c65c7552c1a282c380f7c38048e4286b4b39
https://github.com/scummvm/scummvm/commit/3ef9c65c7552c1a282c380f7c38048e4286b4b39
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: make the endgame for driller more stable
Changed paths:
engines/freescape/freescape.cpp
engines/freescape/games/dark/dark.cpp
engines/freescape/games/driller/driller.cpp
engines/freescape/games/palettes.cpp
engines/freescape/gfx.cpp
engines/freescape/language/instruction.cpp
engines/freescape/loaders/8bitBinaryLoader.cpp
diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index 1788c584ed9..fec9c249c6c 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -458,7 +458,10 @@ void FreescapeEngine::checkSensors() {
void FreescapeEngine::drawSensorShoot(Sensor *sensor) {}
void FreescapeEngine::flashScreen(int backgroundColor) {
- if (backgroundColor >= 16)
+ // CPC area colors are stored as 0..31 ink values, not 0..15 palette slots.
+ // Driller uses these raw values directly in the area headers and the original
+ // CPC code feeds them to the hardware/Gate Array without clamping.
+ if (backgroundColor >= (isCPC() ? 32 : 16))
return;
_currentArea->remapColor(_currentArea->_usualBackgroundColor, backgroundColor);
_currentArea->remapColor(_currentArea->_skyColor, backgroundColor);
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index 5360285998d..d44160db45b 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -710,8 +710,13 @@ void DarkEngine::gotoArea(uint16 areaID, int entranceID) {
_gfx->setColorRemaps(&_currentArea->_colorRemaps);
swapPalette(areaID);
- _currentArea->_skyColor = isCPC() ? 1 : 0;
- _currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
+ if (isCPC()) {
+ // The CPC loader still uses the generic area header parser, but the
+ // original Driller code does not use the first header byte as split
+ // sky/ground colors. Keep the real per-area background ink and reuse it
+ // for the viewport fill instead of interpreting the flag nibble as a sky.
+ _currentArea->_skyColor = _currentArea->_usualBackgroundColor;
+ }
resetInput();
}
diff --git a/engines/freescape/games/driller/driller.cpp b/engines/freescape/games/driller/driller.cpp
index 19b07cd85b1..b5c0b04c7dd 100644
--- a/engines/freescape/games/driller/driller.cpp
+++ b/engines/freescape/games/driller/driller.cpp
@@ -288,7 +288,7 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
debugC(1, kFreescapeDebugMove, "starting player position: %f, %f, %f", _position.x(), _position.y(), _position.z());
clearTemporalMessages();
- // Ignore sky/ground fields
+ // DOS/Amiga/Atari ST ignore the area sky/background fields here.
_gfx->_keyColor = 0;
_gfx->setColorRemaps(&_currentArea->_colorRemaps);
@@ -298,8 +298,11 @@ void DrillerEngine::gotoArea(uint16 areaID, int entranceID) {
_currentArea->_skyColor = 0;
_currentArea->_usualBackgroundColor = 0;
} else if (isCPC()) {
- _currentArea->_usualBackgroundColor = 1;
- _currentArea->_skyColor = 1;
+ // The CPC loader still uses the generic area header parser, but the
+ // original Driller code does not use the first header byte as split
+ // sky/ground colors. Keep the real per-area background ink and reuse it
+ // for the viewport fill instead of interpreting the flag nibble as a sky.
+ _currentArea->_skyColor = _currentArea->_usualBackgroundColor;
}
resetInput();
@@ -915,19 +918,15 @@ bool DrillerEngine::checkIfGameEnded() {
if (_demoData[_demoIndex + 1] == 0x5f)
return true;
- if ((isAmiga() || isAtariST()) && _gameStateControl == kFreescapeGameStatePlaying &&
- _currentArea && _currentArea->getAreaID() == _endArea) {
- stopMovement();
- _endGameDelayTicks = 0;
- _endGameKeyPressed = false;
- _endGamePlayerEndArea = false;
- _amigaAtariEndGameStep = -1;
+ if (_currentArea && _currentArea->getAreaID() == _endArea &&
+ _gameStateControl == kFreescapeGameStatePlaying &&
+ _gameStateVars[32] == 18
+ ) {
_gameStateControl = kFreescapeGameStateEnd;
return true;
}
- FreescapeEngine::checkIfGameEnded();
- return false;
+ return FreescapeEngine::checkIfGameEnded();
}
bool DrillerEngine::triggerWinCondition() {
@@ -941,14 +940,15 @@ bool DrillerEngine::triggerWinCondition() {
}
void DrillerEngine::endGame() {
- if (isAmiga() || isAtariST()) {
- _shootingFrames = 0;
- _delayedShootObject = nullptr;
+ if (!_currentArea || _currentArea->getAreaID() != _endArea)
+ gotoArea(_endArea, _endEntrance);
+
+ _shootingFrames = 0;
+ _delayedShootObject = nullptr;
+ if (isAmiga() || isAtariST()) {
if (_amigaAtariEndGameStep < 0) {
stopMovement();
- if (!_currentArea || _currentArea->getAreaID() != _endArea)
- gotoArea(_endArea, _endEntrance);
// ENDGAME on Amiga/Atari ST switches to set 127 and then runs a 21-step
// flythrough. The original Amiga coordinates are stored at twice the engine
@@ -963,33 +963,19 @@ void DrillerEngine::endGame() {
_endGamePlayerEndArea = false;
_amigaAtariEndGameStep = 0;
- for (int step = 0; step < 21; step++) {
+ for (int step = 0; step < 20; step++) {
_position.z() += 400.0f / areaScale;
_position.y() -= 140.0f / areaScale;
_lastPosition = _position;
waitInLoop(5);
}
-
- waitInLoop(0); // Final BT01 before LIFTUP
waitInLoop(102);
- _endGamePlayerEndArea = true;
- waitInLoop(500);
- _gameStateControl = kFreescapeGameStateRestart;
}
- _endGameKeyPressed = false;
- return;
} else {
- FreescapeEngine::endGame();
-
- if (!_endGamePlayerEndArea)
- return;
- }
-
- if (_endGameKeyPressed) {
- _gameStateControl = kFreescapeGameStateRestart;
+ waitInLoop(100);
}
- _endGameKeyPressed = false;
+ _gameStateControl = kFreescapeGameStateRestart;
}
bool DrillerEngine::onScreenControls(Common::Point mouse) {
diff --git a/engines/freescape/games/palettes.cpp b/engines/freescape/games/palettes.cpp
index 7786d96fd0a..a5c9ee732d2 100644
--- a/engines/freescape/games/palettes.cpp
+++ b/engines/freescape/games/palettes.cpp
@@ -107,38 +107,38 @@ byte kDrillerZXPalette[9][3] = {
};
byte kDrillerCPCPalette[32][3] = {
- {0x80, 0x80, 0x80}, // 0: special case?
- {0x00, 0x00, 0x00}, // 1: used in dark only?
- {0x00, 0x80, 0xff}, // 2
- {0xff, 0xff, 0x80}, // 3
- {0x00, 0x00, 0x80}, // 4
- {0xff, 0x00, 0x80}, // 5
- {0x00, 0x80, 0x80}, // 6
- {0xff, 0x80, 0x80}, // 7
- {0x80, 0x00, 0xff}, // 8
- {0x00, 0x80, 0x00}, // 9
- {0xff, 0xff, 0x00}, // 10
- {0xff, 0xff, 0xff}, // 11
- {0xff, 0x00, 0x00}, // 12
- {0x11, 0x22, 0x33},
- {0xff, 0x80, 0x00}, // 14
- {0xff, 0x80, 0xff}, // 15
- {0x11, 0x22, 0x33},
- {0x00, 0xff, 0x80}, // 17
- {0x00, 0xff, 0x00}, // 18
- {0x80, 0xff, 0xff}, // 19
- {0x80, 0x80, 0x80}, // 20
- {0x00, 0x00, 0xff}, // 21
- {0x00, 0x80, 0x00}, // 22
- {0x00, 0x80, 0xff}, // 23
- {0x80, 0x00, 0x80}, // 24
- {0x80, 0xff, 0x80}, // 25
- {0x80, 0xff, 0x00}, // 26
- {0x00, 0xff, 0xff}, // 27
- {0x80, 0x00, 0x00}, // 28
- {0x80, 0x00, 0xff}, // 29
- {0x80, 0x80, 0x00}, // 30
- {0x80, 0x80, 0xff}, // 31
+ {0x80, 0x80, 0x80}, // 0: white
+ {0x80, 0x80, 0x80}, // 1: white
+ {0x00, 0xff, 0x80}, // 2: sea green
+ {0xff, 0xff, 0x80}, // 3: pastel yellow
+ {0x00, 0x00, 0x80}, // 4: blue
+ {0xff, 0x00, 0x80}, // 5: purple
+ {0x00, 0x80, 0x80}, // 6: cyan
+ {0xff, 0x80, 0x80}, // 7: pink
+ {0xff, 0x00, 0x80}, // 8: purple
+ {0xff, 0xff, 0x80}, // 9: pastel yellow
+ {0xff, 0xff, 0x00}, // 10: bright yellow
+ {0xff, 0xff, 0xff}, // 11: bright white
+ {0xff, 0x00, 0x00}, // 12: bright red
+ {0xff, 0x00, 0xff}, // 13: bright magenta
+ {0xff, 0x80, 0x00}, // 14: orange
+ {0xff, 0x80, 0xff}, // 15: pastel magenta
+ {0x00, 0x00, 0x80}, // 16: blue
+ {0x00, 0xff, 0x80}, // 17: sea green
+ {0x00, 0xff, 0x00}, // 18: bright green
+ {0x00, 0xff, 0xff}, // 19: bright cyan
+ {0x00, 0x00, 0x00}, // 20: black
+ {0x00, 0x00, 0xff}, // 21: bright blue
+ {0x00, 0x80, 0x00}, // 22: green
+ {0x00, 0x80, 0xff}, // 23: sky blue
+ {0x80, 0x00, 0x80}, // 24: magenta
+ {0x80, 0xff, 0x80}, // 25: pastel green
+ {0x80, 0xff, 0x00}, // 26: lime
+ {0x80, 0xff, 0xff}, // 27: pastel cyan
+ {0x80, 0x00, 0x00}, // 28: red
+ {0x80, 0x00, 0xff}, // 29: mauve
+ {0x80, 0x80, 0x00}, // 30: yellow
+ {0x80, 0x80, 0xff}, // 31: pastel blue
};
void FreescapeEngine::loadColorPalette() {
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 0c1df92916c..329403f668a 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -494,6 +494,18 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
return true;
}
assert(_renderMode == Common::kRenderCPC);
+
+ // Driller CPC area/background colors are raw 0..31 ink values. Only
+ // Freescape drawable colors 1..15 use the packed four-pen color map.
+ if (index >= _colorMap->size() + 1) {
+ readFromPalette(index, r1, g1, b1);
+ r2 = r1;
+ g2 = g1;
+ b2 = b1;
+ stipple = nullptr;
+ return true;
+ }
+
stipple = (byte *)_stipples[index - 1];
byte *entry = (*_colorMap)[index - 1];
uint8 i1 = getCPCPixel(entry[0], 0, true);
diff --git a/engines/freescape/language/instruction.cpp b/engines/freescape/language/instruction.cpp
index c67acf8abda..6b2763dc616 100644
--- a/engines/freescape/language/instruction.cpp
+++ b/engines/freescape/language/instruction.cpp
@@ -355,8 +355,12 @@ void FreescapeEngine::executeRedraw(FCLInstruction &instruction) {
if (isEclipse2() && _currentArea->getAreaID() == _startArea && _gameStateControl == kFreescapeGameStateStart)
delay = delay * 10;
- if (isCastle() && (isSpectrum() || isCPC()) && getGameBit(31))
+ if (isCastle() && (isSpectrum() || isCPC() || isC64()) && getGameBit(31))
delay = delay * 15; // Slow down redraws when the final cutscene is playing
+
+ if (isDriller() && (isSpectrum() || isCPC() || isC64()) && _gameStateVars[32] == 18)
+ delay = delay * 15; // Slow down redraws when the final cutscene is playing
+
waitInLoop(delay);
}
diff --git a/engines/freescape/loaders/8bitBinaryLoader.cpp b/engines/freescape/loaders/8bitBinaryLoader.cpp
index b033012f6d3..1be36892b96 100644
--- a/engines/freescape/loaders/8bitBinaryLoader.cpp
+++ b/engines/freescape/loaders/8bitBinaryLoader.cpp
@@ -652,6 +652,9 @@ Area *FreescapeEngine::load8bitArea(Common::SeekableReadStream *file, uint16 nco
uint8 inkColor = 0;
if (!(isCastle() && (isSpectrum() || isCPC() || isC64()))) {
+ // On Driller/Dark/Eclipse 8-bit targets these are four consecutive
+ // per-area color bytes. CPC stores raw 0..31 ink values here, matching
+ // the original area descriptor layout at offsets +6..+9.
usualBackgroundColor = readField(file, 8);
underFireBackgroundColor = readField(file, 8);
paperColor = readField(file, 8);
Commit: 476425a3d3790eb0a8c769abe2332020da235dd6
https://github.com/scummvm/scummvm/commit/476425a3d3790eb0a8c769abe2332020da235dd6
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: avoid heartbeat to beat too fast
Changed paths:
engines/freescape/freescape.cpp
engines/freescape/games/eclipse/eclipse.cpp
diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index fec9c249c6c..a682eff3f1c 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -937,7 +937,7 @@ void FreescapeEngine::endGame() {
_shootingFrames = 0;
_delayedShootObject = nullptr;
- if (_gameStateControl == kFreescapeGameStateEnd && !isPlayingSound() && !_endGamePlayerEndArea) {
+ if (_gameStateControl == kFreescapeGameStateEnd && !_endGamePlayerEndArea) {
_endGamePlayerEndArea = true;
gotoArea(_endArea, _endEntrance);
}
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 7a81e20f5dc..a9cb8f9f0d8 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -824,7 +824,7 @@ void EclipseEngine::drawHeartIndicator(Graphics::Surface *surface, int x, int y)
int beatStart = MAX(beatCycle - 5, 0);
int frame = _lastHeartIndicatorFrame;
- if (_avoidRenderingFrames > 0 || _hasFallen) {
+ if (shield <= 5 || _avoidRenderingFrames > 0 || _hasFallen) {
frame = 1;
_lastHeartIndicatorFrame = frame;
} else if (!_inWaitLoop) {
Commit: b300be71a3707c4016891d2a8ce5ff59e1d4fd92
https://github.com/scummvm/scummvm/commit/b300be71a3707c4016891d2a8ce5ff59e1d4fd92
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: loading of cpc images directly from data in dark cpc
Changed paths:
engines/freescape/games/dark/cpc.cpp
engines/freescape/games/dark/dark.cpp
engines/freescape/games/dark/dark.h
engines/freescape/games/eclipse/eclipse.cpp
diff --git a/engines/freescape/games/dark/cpc.cpp b/engines/freescape/games/dark/cpc.cpp
index c29748f5178..9f67d7e1701 100644
--- a/engines/freescape/games/dark/cpc.cpp
+++ b/engines/freescape/games/dark/cpc.cpp
@@ -60,6 +60,60 @@ byte kCPCPaletteDarkTitle[16][3] = {
{0x00, 0x80, 0x00}, // 15: X
};
+void DarkEngine::loadCPCIndicatorData(const byte *data, int widthBytes, int height, Common::Array<Graphics::ManagedSurface *> &target) {
+ Graphics::ManagedSurface *surface = new Graphics::ManagedSurface();
+ surface->create(widthBytes * 4, height, Graphics::PixelFormat::createFormatCLUT8());
+ surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0);
+ Common::MemoryReadStream stream(data, widthBytes * height);
+ target.push_back(loadFrameCPCIndexed(&stream, surface, widthBytes, height));
+}
+
+void DarkEngine::loadCPCIndicator(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &target) {
+ // The HUD blitter at 0x7938 consumes records as { height, widthBytes, mode-1 row data... }.
+ file->seek(offset);
+ int height = file->readByte();
+ int widthBytes = file->readByte();
+ Common::Array<byte> data;
+ data.resize(widthBytes * height);
+ file->read(data.data(), data.size());
+ loadCPCIndicatorData(data.data(), widthBytes, height, target);
+}
+
+void DarkEngine::loadCPCIndicators(Common::SeekableReadStream *file) {
+ for (auto &indicator : _cpcIndicators) {
+ indicator->free();
+ delete indicator;
+ }
+ _cpcIndicators.clear();
+ for (auto &indicator : _cpcJetpackIndicators) {
+ indicator->free();
+ delete indicator;
+ }
+ _cpcJetpackIndicators.clear();
+ for (auto &indicator : _cpcActionIndicators) {
+ indicator->free();
+ delete indicator;
+ }
+ _cpcActionIndicators.clear();
+
+ loadCPCIndicator(file, 0x0F04, _cpcIndicators); // 0x6BFE -> 0x2AE6
+ loadCPCIndicator(file, 0x0E8A, _cpcIndicators); // 0x6C11 -> 0x2A6C
+ loadCPCIndicator(file, 0x0E10, _cpcIndicators); // 0x6C1B -> 0x29F2
+ loadCPCIndicator(file, 0x0D8F, _cpcIndicators); // 0x6C08 -> 0x2971
+
+ byte frame0[6];
+ byte frame1[6];
+ file->seek(0x0E09); // 0x52C6 -> 0x29EB
+ file->read(frame0, 6);
+ file->seek(0x0E0A); // 0x52CB -> 0x29EC
+ file->read(frame1, 6);
+ loadCPCIndicatorData(frame0, 1, 6, _cpcJetpackIndicators);
+ loadCPCIndicatorData(frame1, 1, 6, _cpcJetpackIndicators);
+
+ loadCPCIndicator(file, 0x0F7E, _cpcActionIndicators); // 0x507E -> 0x2B60
+ loadCPCIndicator(file, 0x0FB2, _cpcActionIndicators); // 0x508B -> 0x2B94
+}
+
void DarkEngine::loadAssetsCPCFullGame() {
Common::File file;
@@ -90,13 +144,7 @@ void DarkEngine::loadAssetsCPCFullGame() {
loadGlobalObjects(&file, 0x9a, 23);
load8bitBinary(&file, 0x6255, 16);
loadSoundsCPC(&file, 0x09B7, 160, 0x0A57, 284, 0x0B73, 203);
- _indicators.push_back(loadBundledImage("dark_fallen_indicator"));
- _indicators.push_back(loadBundledImage("dark_crouch_indicator"));
- _indicators.push_back(loadBundledImage("dark_walk_indicator"));
- _indicators.push_back(loadBundledImage("dark_jet_indicator"));
-
- for (auto &it : _indicators)
- it->convertToInPlace(_gfx->_texturePixelFormat);
+ loadCPCIndicators(&file);
}
void DarkEngine::drawCPCUI(Graphics::Surface *surface) {
@@ -165,7 +213,7 @@ void DarkEngine::drawCPCUI(Graphics::Surface *surface) {
surface->fillRect(energyBar, front);
}
drawBinaryClock(surface, 300, 124, front, back);
- drawIndicator(surface, 160, 136);
+ drawIndicator(surface, 160, 140);
drawVerticalCompass(surface, 24, 76, _pitch, front);
}
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index d44160db45b..d3528306f7a 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -99,11 +99,25 @@ DarkEngine::DarkEngine(OSystem *syst, const ADGameDescription *gd) : FreescapeEn
_jetpackIndicatorTransitionFrame = 0;
_jetpackIndicatorTransitionDirection = 0;
_jetpackIndicatorNextFrameMillis = 0;
+ _cpcActionIndicatorUntilMillis = 0;
}
DarkEngine::~DarkEngine() {
delete _playerC64Sfx;
delete _playerC64Music;
+
+ for (auto &indicator : _cpcIndicators) {
+ indicator->free();
+ delete indicator;
+ }
+ for (auto &indicator : _cpcJetpackIndicators) {
+ indicator->free();
+ delete indicator;
+ }
+ for (auto &indicator : _cpcActionIndicators) {
+ indicator->free();
+ delete indicator;
+ }
}
void DarkEngine::addECDs(Area *area) {
@@ -722,6 +736,9 @@ void DarkEngine::gotoArea(uint16 areaID, int entranceID) {
}
void DarkEngine::pressedKey(const int keycode) {
+ if (isCPC())
+ _cpcActionIndicatorUntilMillis = g_system->getMillis() + 150;
+
// This code is duplicated in the DrillerEngine::pressedKey (except for the J case)
if (keycode == kActionIncreaseStepSize) {
increaseStepSize();
@@ -894,6 +911,11 @@ void DarkEngine::drawHorizontalCompass(int x, int y, float angle, uint32 front,
uint32 transparent = _gfx->_texturePixelFormat.ARGBToColor(0x00, 0x00, 0x00, 0x00);
uint32 green = _gfx->_texturePixelFormat.ARGBToColor(0xff, 0x00, 0xaa, 0x00);
+ if (isCPC()) {
+ uint8 r, g, b;
+ _gfx->selectColorFromFourColorPalette(3, r, g, b);
+ green = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+ }
int delta = (angle - 180) / 5.5;
Common::String compass = "-N-E-S-W-N-E-S";
@@ -913,7 +935,55 @@ void DarkEngine::drawHorizontalCompass(int x, int y, float angle, uint32 front,
surface->fillRect(Common::Rect(x + 80, y - 5, 320, y + 10), transparent);
}
+void DarkEngine::drawCPCSprite(Graphics::Surface *surface, const Graphics::ManagedSurface *indicator, int xPosition, int yPosition) {
+ uint32 colors[4];
+ for (uint8 i = 0; i < 4; i++) {
+ uint8 r, g, b;
+ _gfx->selectColorFromFourColorPalette(i, r, g, b);
+ colors[i] = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
+ }
+
+ for (int y = 0; y < indicator->h; y++) {
+ for (int x = 0; x < indicator->w; x++)
+ surface->setPixel(xPosition + x, yPosition + y, colors[(byte)indicator->getPixel(x, y)]);
+ }
+}
+
+void DarkEngine::drawCPCIndicator(Graphics::Surface *surface, int xPosition, int yPosition) {
+ if (_cpcIndicators.empty())
+ return;
+
+ const Graphics::ManagedSurface *indicator = nullptr;
+ if (_hasFallen)
+ indicator = _cpcIndicators[0];
+ else if (_flyMode)
+ indicator = _cpcIndicators[3];
+ else if (_playerHeightNumber == 0)
+ indicator = _cpcIndicators[1];
+ else
+ indicator = _cpcIndicators[2];
+
+ drawCPCSprite(surface, indicator, xPosition, yPosition);
+
+ if (_flyMode && _cpcJetpackIndicators.size() > 1) {
+ uint32 frame = (g_system->getMillis() / 40) & 1;
+ drawCPCSprite(surface, _cpcJetpackIndicators[frame], xPosition + 4, yPosition + 12);
+ }
+
+ bool showActionIndicator = _moveForward || _moveBackward || _moveUp || _moveDown ||
+ _shootingFrames > 0 || g_system->getMillis() < _cpcActionIndicatorUntilMillis;
+ if (showActionIndicator && _cpcActionIndicators.size() > 1) {
+ uint32 frame = (g_system->getMillis() / 40) & 1;
+ drawCPCSprite(surface, _cpcActionIndicators[frame], 256, 169);
+ }
+}
+
void DarkEngine::drawIndicator(Graphics::Surface *surface, int xPosition, int yPosition) {
+ if (isCPC() && !_cpcIndicators.empty()) {
+ drawCPCIndicator(surface, xPosition, yPosition);
+ return;
+ }
+
if (_indicators.size() == 0)
return;
if (_hasFallen)
diff --git a/engines/freescape/games/dark/dark.h b/engines/freescape/games/dark/dark.h
index 6153cca0326..984bed34cf5 100644
--- a/engines/freescape/games/dark/dark.h
+++ b/engines/freescape/games/dark/dark.h
@@ -105,6 +105,10 @@ public:
Font _fontBig;
Font _fontMedium;
Font _fontSmall;
+ Common::Array<Graphics::ManagedSurface *> _cpcIndicators;
+ Common::Array<Graphics::ManagedSurface *> _cpcJetpackIndicators;
+ Common::Array<Graphics::ManagedSurface *> _cpcActionIndicators;
+ uint32 _cpcActionIndicatorUntilMillis;
// Dark Side Amiga stores the grounded jetpack indicator states as raw
// 4-plane bitplane data. The executable drives those frames through a tiny
@@ -158,6 +162,11 @@ private:
bool tryDestroyECD(int index);
bool tryDestroyECDFullGame(int index);
void addWalls(Area *area);
+ void loadCPCIndicator(Common::SeekableReadStream *file, uint32 offset, Common::Array<Graphics::ManagedSurface *> &target);
+ void loadCPCIndicatorData(const byte *data, int widthBytes, int height, Common::Array<Graphics::ManagedSurface *> &target);
+ void loadCPCIndicators(Common::SeekableReadStream *file);
+ void drawCPCSprite(Graphics::Surface *surface, const Graphics::ManagedSurface *indicator, int xPosition, int yPosition);
+ void drawCPCIndicator(Graphics::Surface *surface, int xPosition, int yPosition);
void drawVerticalCompass(Graphics::Surface *surface, int x, int y, float angle, uint32 color);
void drawHorizontalCompass(int x, int y, float angle, uint32 front, uint32 back, Graphics::Surface *surface);
};
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index a9cb8f9f0d8..9512682f00a 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -363,7 +363,7 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
_gfx->_keyColor = 0;
swapPalette(areaID);
- _currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
+ //_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
if (isAmiga() || isAtariST())
_currentArea->_skyColor = 15;
Commit: 167355fb19e5761823ca60289fb07ab8042c3e93
https://github.com/scummvm/scummvm/commit/167355fb19e5761823ca60289fb07ab8042c3e93
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: wait before restoring ECD in dark
Changed paths:
engines/freescape/games/dark/dark.cpp
diff --git a/engines/freescape/games/dark/dark.cpp b/engines/freescape/games/dark/dark.cpp
index d3528306f7a..c16c082cb89 100644
--- a/engines/freescape/games/dark/dark.cpp
+++ b/engines/freescape/games/dark/dark.cpp
@@ -553,6 +553,8 @@ bool DarkEngine::checkIfGameEnded() {
int index = _gameStateVars[kVariableDarkECD] - 1;
bool destroyed = tryDestroyECD(index);
+ waitInLoop(10);
+
if (destroyed) {
_gameStateVars[kVariableActiveECDs] -= 4;
_gameStateVars[k8bitVariableScore] += 52750;
Commit: 43ca8476727a462e24b54112bae25a60da93c3ac
https://github.com/scummvm/scummvm/commit/43ca8476727a462e24b54112bae25a60da93c3ac
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: correctly render stipple patterns in CPC
Changed paths:
engines/freescape/gfx.cpp
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 329403f668a..b7b53fdc1e7 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -507,9 +507,9 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
}
stipple = (byte *)_stipples[index - 1];
- byte *entry = (*_colorMap)[index - 1];
- uint8 i1 = getCPCPixel(entry[0], 0, true);
- uint8 i2 = getCPCPixel(entry[0], 1, true);
+ byte pair = _colorPair[index - 1];
+ uint8 i1 = pair & 0xf;
+ uint8 i2 = (pair >> 4) & 0xf;
selectColorFromFourColorPalette(i1, r1, g1, b1);
selectColorFromFourColorPalette(i2, r2, g2, b2);
if (r1 == r2 && g1 == g2 && b1 == b2) {
Commit: e2d16ffcecbf7e7d6fb25a6156ca1155a7605355
https://github.com/scummvm/scummvm/commit/e2d16ffcecbf7e7d6fb25a6156ca1155a7605355
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-03-29T15:15:54+02:00
Commit Message:
FREESCAPE: better rendering from driller/castle cpc
Changed paths:
engines/freescape/freescape.cpp
engines/freescape/freescape.h
engines/freescape/games/castle/castle.cpp
engines/freescape/games/castle/cpc.cpp
engines/freescape/games/driller/cpc.cpp
engines/freescape/games/eclipse/eclipse.cpp
engines/freescape/games/palettes.cpp
engines/freescape/gfx.cpp
engines/freescape/loaders/8bitBinaryLoader.cpp
diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index a682eff3f1c..3c2de11785e 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -39,6 +39,20 @@ namespace Freescape {
FreescapeEngine *g_freescape;
+bool isEncodedCPCDirectColor(uint8 index) {
+ return index >= 64 && index < 96;
+}
+
+uint8 encodeCPCDirectColor(uint8 index) {
+ assert(index < 32);
+ return index + 64;
+}
+
+uint8 decodeCPCDirectColor(uint8 index) {
+ assert(isEncodedCPCDirectColor(index));
+ return index - 64;
+}
+
byte getCPCPixelMode1(byte cpc_byte, int index) {
if (index == 0)
return ((cpc_byte & 0x08) >> 2) | ((cpc_byte & 0x80) >> 7);
@@ -458,11 +472,13 @@ void FreescapeEngine::checkSensors() {
void FreescapeEngine::drawSensorShoot(Sensor *sensor) {}
void FreescapeEngine::flashScreen(int backgroundColor) {
- // CPC area colors are stored as 0..31 ink values, not 0..15 palette slots.
- // Driller uses these raw values directly in the area headers and the original
- // CPC code feeds them to the hardware/Gate Array without clamping.
- if (backgroundColor >= (isCPC() ? 32 : 16))
+ if (isCPC()) {
+ if (backgroundColor >= 32 && !isEncodedCPCDirectColor(backgroundColor))
+ return;
+ } else if (backgroundColor >= 16) {
return;
+ }
+
_currentArea->remapColor(_currentArea->_usualBackgroundColor, backgroundColor);
_currentArea->remapColor(_currentArea->_skyColor, backgroundColor);
drawFrame();
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 22c19b9ccab..3ea2f51b933 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -53,6 +53,10 @@ namespace Freescape {
class Renderer;
class Debugger;
+bool isEncodedCPCDirectColor(uint8 index);
+uint8 encodeCPCDirectColor(uint8 index);
+uint8 decodeCPCDirectColor(uint8 index);
+
#define FREESCAPE_DATA_BUNDLE "freescape.dat"
enum CameraMovement {
diff --git a/engines/freescape/games/castle/castle.cpp b/engines/freescape/games/castle/castle.cpp
index 008f0c5f32f..6cc0ba831c4 100644
--- a/engines/freescape/games/castle/castle.cpp
+++ b/engines/freescape/games/castle/castle.cpp
@@ -598,6 +598,7 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
// Ignore sky/ground fields
_gfx->_keyColor = 0;
_gfx->clearColorPairArray();
+ _gfx->fillColorPairArray();
swapPalette(areaID);
@@ -637,14 +638,10 @@ void CastleEngine::gotoArea(uint16 areaID, int entranceID) {
if (isSpectrum())
_gfx->_paperColor = 0;
- // Unclear why this is needed
if (isCPC()) {
- for (int i = 0; i < 128; i++) {
- _gfx->_stipples[2][i] = _gfx->_stipples[11][i];
- }
- ColorMap *cm = _gfx->_colorMap;
- (*cm)[2] = (*cm)[11];
+ _currentArea->_skyColor = _currentArea->_usualBackgroundColor;
}
+
resetInput();
if (entranceID > 0) {
diff --git a/engines/freescape/games/castle/cpc.cpp b/engines/freescape/games/castle/cpc.cpp
index 4c92dc765ca..cc8dc6d0ead 100644
--- a/engines/freescape/games/castle/cpc.cpp
+++ b/engines/freescape/games/castle/cpc.cpp
@@ -489,9 +489,10 @@ void CastleEngine::drawCPCUI(Graphics::Surface *surface) {
_gfx->readFromPalette(color, r, g, b);
uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
- color = 1;
-
- _gfx->readFromPalette(color, r, g, b);
+ // Castle CPC draws the message strip text through the UI row-pointer path,
+ // and the original string routine erases with zero pixels there. In the CPC
+ // HUD palette that is pen 0, i.e. black, not global CPC palette index 0.
+ _gfx->selectColorFromFourColorPalette(0, r, g, b);
uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
Common::Rect backRect(97, 181, 232, 190);
diff --git a/engines/freescape/games/driller/cpc.cpp b/engines/freescape/games/driller/cpc.cpp
index a0cda34d34f..73a4436ebc2 100644
--- a/engines/freescape/games/driller/cpc.cpp
+++ b/engines/freescape/games/driller/cpc.cpp
@@ -164,6 +164,8 @@ void DrillerEngine::drawCPCUI(Graphics::Surface *surface) {
uint32 color = _currentArea->_underFireBackgroundColor;
uint8 r, g, b;
+ if (isEncodedCPCDirectColor(color))
+ color = decodeCPCDirectColor(color);
_gfx->readFromPalette(color, r, g, b);
uint32 front = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
@@ -172,6 +174,8 @@ void DrillerEngine::drawCPCUI(Graphics::Surface *surface) {
color = (*_gfx->_colorRemaps)[color];
}
+ if (isEncodedCPCDirectColor(color))
+ color = decodeCPCDirectColor(color);
_gfx->readFromPalette(color, r, g, b);
uint32 back = _gfx->_texturePixelFormat.ARGBToColor(0xFF, r, g, b);
diff --git a/engines/freescape/games/eclipse/eclipse.cpp b/engines/freescape/games/eclipse/eclipse.cpp
index 9512682f00a..b984bffca00 100644
--- a/engines/freescape/games/eclipse/eclipse.cpp
+++ b/engines/freescape/games/eclipse/eclipse.cpp
@@ -363,7 +363,6 @@ void EclipseEngine::gotoArea(uint16 areaID, int entranceID) {
_gfx->_keyColor = 0;
swapPalette(areaID);
- //_currentArea->_usualBackgroundColor = isCPC() ? 1 : 0;
if (isAmiga() || isAtariST())
_currentArea->_skyColor = 15;
diff --git a/engines/freescape/games/palettes.cpp b/engines/freescape/games/palettes.cpp
index a5c9ee732d2..933dea17616 100644
--- a/engines/freescape/games/palettes.cpp
+++ b/engines/freescape/games/palettes.cpp
@@ -232,6 +232,15 @@ void FreescapeEngine::swapPalette(uint16 levelID) {
_gfx->_paperColor = _areaMap[levelID]->_paperColor;
_gfx->_underFireBackgroundColor = _areaMap[levelID]->_underFireBackgroundColor;
+ if (isCPC()) {
+ if (isEncodedCPCDirectColor(_gfx->_inkColor))
+ _gfx->_inkColor = decodeCPCDirectColor(_gfx->_inkColor);
+ if (isEncodedCPCDirectColor(_gfx->_paperColor))
+ _gfx->_paperColor = decodeCPCDirectColor(_gfx->_paperColor);
+ if (isEncodedCPCDirectColor(_gfx->_underFireBackgroundColor))
+ _gfx->_underFireBackgroundColor = decodeCPCDirectColor(_gfx->_underFireBackgroundColor);
+ }
+
if (isC64()) {
_gfx->_inkColor %= 16;
_gfx->_paperColor %= 16;
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index b7b53fdc1e7..0d8742d134b 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -29,7 +29,7 @@
#include "graphics/opengl/context.h"
#endif
-#include "freescape/gfx.h"
+#include "freescape/freescape.h"
#include "freescape/objects/object.h"
namespace Freescape {
@@ -260,7 +260,9 @@ void Renderer::setColorMap(ColorMap *colorMap_) {
}
} else if (_renderMode == Common::kRenderCPC) {
fillColorPairArray();
- for (int i = 4; i < 15; i++) {
+ // Castle CPC uses color-map entry 3 as a genuine checker pattern,
+ // so CPC stipples need to be generated for all 15 Freescape entries.
+ for (int i = 0; i < 15; i++) {
byte pair = _colorPair[i];
byte c1 = pair & 0xf;
byte c2 = (pair >> 4) & 0xf;
@@ -382,6 +384,8 @@ bool Renderer::getRGBAtC64(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
stipple = nullptr;
return true;
}
+ if (isEncodedCPCDirectColor(index))
+ index = decodeCPCDirectColor(index);
readFromPalette(index, r1, g1, b1);
r2 = r1;
g2 = g1;
@@ -486,6 +490,8 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
stipple = nullptr;
return true;
}
+ if (isEncodedCPCDirectColor(index))
+ index = decodeCPCDirectColor(index);
readFromPalette(index, r1, g1, b1);
r2 = r1;
g2 = g1;
@@ -495,9 +501,8 @@ bool Renderer::getRGBAtCPC(uint8 index, uint8 &r1, uint8 &g1, uint8 &b1, uint8 &
}
assert(_renderMode == Common::kRenderCPC);
- // Driller CPC area/background colors are raw 0..31 ink values. Only
- // Freescape drawable colors 1..15 use the packed four-pen color map.
- if (index >= _colorMap->size() + 1) {
+ if (isEncodedCPCDirectColor(index)) {
+ index = decodeCPCDirectColor(index);
readFromPalette(index, r1, g1, b1);
r2 = r1;
g2 = g1;
@@ -1238,6 +1243,8 @@ void Renderer::drawBackground(uint8 color) {
if (_colorRemaps && _colorRemaps->contains(color)) {
color = (*_colorRemaps)[color];
+ if (_renderMode == Common::kRenderCPC && isEncodedCPCDirectColor(color))
+ color = decodeCPCDirectColor(color);
readFromPalette(color, r1, g1, b1);
clear(r1, g1, b1);
return;
diff --git a/engines/freescape/loaders/8bitBinaryLoader.cpp b/engines/freescape/loaders/8bitBinaryLoader.cpp
index 1be36892b96..12b9b55f4c6 100644
--- a/engines/freescape/loaders/8bitBinaryLoader.cpp
+++ b/engines/freescape/loaders/8bitBinaryLoader.cpp
@@ -672,6 +672,17 @@ Area *FreescapeEngine::load8bitArea(Common::SeekableReadStream *file, uint16 nco
skyColor = 0;
}
+ if (isCPC() && isDriller()) {
+ // Driller CPC stores the four area colors at bytes +6..+9 as raw 0..31
+ // CPC ink values. The original code copies them straight into the live
+ // pens before programming the hardware, so encode them in the Area and
+ // let the generic renderer treat them as direct CPC inks.
+ usualBackgroundColor = encodeCPCDirectColor(usualBackgroundColor);
+ underFireBackgroundColor = encodeCPCDirectColor(underFireBackgroundColor);
+ paperColor = encodeCPCDirectColor(paperColor);
+ inkColor = encodeCPCDirectColor(inkColor);
+ }
+
debugC(1, kFreescapeDebugParser, "Colors usual background: %d", usualBackgroundColor);
debugC(1, kFreescapeDebugParser, "Colors under fire background: %d", underFireBackgroundColor);
debugC(1, kFreescapeDebugParser, "Color Paper: %d", paperColor);
More information about the Scummvm-git-logs
mailing list