[Scummvm-git-logs] scummvm master -> 4fa1ca2ea34e8bc7274081e82b601af29ca412ac
neuromancer
noreply at scummvm.org
Fri Sep 25 11:35:35 UTC 2026
This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
0e3d9c6591 COLONY: stair related render and sound fixes
c0b89770e8 COLONY: new command and adjusted door behavior when power is off
ba109d8eb2 COLONY: improved end game animations
4fa1ca2ea3 COLONY: adjust text scrolling speed in intro
Commit: 0e3d9c659152757139c392c6da2b90895e6cd317
https://github.com/scummvm/scummvm/commit/0e3d9c659152757139c392c6da2b90895e6cd317
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-25T13:34:53+02:00
Commit Message:
COLONY: stair related render and sound fixes
Changed paths:
engines/colony/colony.h
engines/colony/movement.cpp
engines/colony/render.cpp
engines/colony/render_features.cpp
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 58bbb585aee..7c19b740675 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -729,10 +729,11 @@ private:
void initRobots();
void renderCorridor3D();
void drawWallFeatures3D();
+ bool isWallFeatureFacingCamera(int cellX, int cellY, int direction) const;
void drawWallFeature3D(int cellX, int cellY, int direction);
void drawCellFeature3D(int cellX, int cellY);
void getWallFace3D(int cellX, int cellY, int direction, float corners[4][3]);
- bool isRecessFeature(int x, int y, int direction) const;
+ bool isVisibleRecessFeature(int x, int y, int direction) const;
bool wallSegmentIsOpenWell(int x, int y, uint8 bit) const;
void getWallRecess3D(const float corners[4][3], float farC[4][3]) const;
void recessPoint(const float nearC[4][3], const float farC[4][3], float u, float v, float depth, float out[3]) const;
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index 4e6818edc26..ee2108f0ab0 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -882,16 +882,19 @@ int ColonyEngine::tryPassThroughFeature(int fromX, int fromY, int direction, Loc
if (pobject != &_me)
return 0; // robots don't use stairs
+ // goToDestination() can load another level and overwrite map.
+ const bool goingDown = (map[0] == kWallFeatureDnStairs);
+
// UpStairs(): the forklift cannot be driven up a staircase.
- if (map[0] == kWallFeatureUpStairs && _fl)
+ if (!goingDown && _fl)
return 0;
const int result = goToDestination(map, pobject);
- if (map[0] == kWallFeatureDnStairs && _fl)
+ if (goingDown && _fl)
doDnStairs();
if (result == 2) {
debugC(1, kColonyDebugMove, "Level change via %s: level=%d pos=(%d,%d)",
- map[0] == kWallFeatureUpStairs ? "upstairs" : "downstairs",
+ goingDown ? "downstairs" : "upstairs",
_level, pobject->xindex, pobject->yindex);
}
return result;
diff --git a/engines/colony/render.cpp b/engines/colony/render.cpp
index a35f1ae59e0..33223e58624 100644
--- a/engines/colony/render.cpp
+++ b/engines/colony/render.cpp
@@ -436,19 +436,23 @@ uint8 ColonyEngine::wallAt(int x, int y) const {
return _wall[x][y];
}
-bool ColonyEngine::isRecessFeature(int x, int y, int direction) const {
+bool ColonyEngine::isVisibleRecessFeature(int x, int y, int direction) const {
const uint8 *map = mapFeatureAt(x, y, direction);
if (!map)
return false;
- return map[0] == kWallFeatureUpStairs || map[0] == kWallFeatureDnStairs;
+ return (map[0] == kWallFeatureUpStairs || map[0] == kWallFeatureDnStairs)
+ && _visibleCell[x][y] && isWallFeatureFacingCamera(x, y, direction);
}
-// Bit 0x01 spans (x,y-1)/(x,y); bit 0x02 spans (x-1,y)/(x,y). Either side may
-// record the well.
+// Only remove a wall when drawWallFeatures3D() will replace it with a well.
+// Bit 0x01 spans (x,y-1)/(x,y); bit 0x02 spans (x-1,y)/(x,y).
bool ColonyEngine::wallSegmentIsOpenWell(int x, int y, uint8 bit) const {
+ if (_corePower[_coreIndex] == 0)
+ return false;
+
if (bit == 0x01)
- return isRecessFeature(x, y, kDirSouth) || isRecessFeature(x, y - 1, kDirNorth);
- return isRecessFeature(x, y, kDirWest) || isRecessFeature(x - 1, y, kDirEast);
+ return isVisibleRecessFeature(x, y, kDirSouth) || isVisibleRecessFeature(x, y - 1, kDirNorth);
+ return isVisibleRecessFeature(x, y, kDirWest) || isVisibleRecessFeature(x - 1, y, kDirEast);
}
const uint8 *ColonyEngine::mapFeatureAt(int x, int y, int direction) const {
diff --git a/engines/colony/render_features.cpp b/engines/colony/render_features.cpp
index b8d1a07382f..4e863c7545e 100644
--- a/engines/colony/render_features.cpp
+++ b/engines/colony/render_features.cpp
@@ -585,33 +585,28 @@ float stairStepHeight(const float *vf, const float *vc, int d, int s) {
return vf[d] + (s + 1) / 8.0f * (vc[d] - vf[d]);
}
-void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
- const uint8 *map = mapFeatureAt(cellX, cellY, direction);
- if (!map || map[0] == kWallFeatureNone)
- return;
-
+bool ColonyEngine::isWallFeatureFacingCamera(int cellX, int cellY, int direction) const {
// Backface culling: only draw features for the side facing the camera.
// This prevents backside decorations (like Level 2 lines) from bleeding through.
- // We use non-inclusive comparisons so features remain visible while standing on the boundary.
+ // Keep features visible while standing on the boundary.
switch (direction) {
case kDirNorth:
- if (_me.yloc > (cellY + 1) * 256)
- return;
- break;
+ return _me.yloc <= (cellY + 1) * 256;
case kDirSouth:
- if (_me.yloc < cellY * 256)
- return;
- break;
+ return _me.yloc >= cellY * 256;
case kDirWest:
- if (_me.xloc < cellX * 256)
- return;
- break;
+ return _me.xloc >= cellX * 256;
case kDirEast:
- if (_me.xloc > (cellX + 1) * 256)
- return;
- break;
- default: break;
+ return _me.xloc <= (cellX + 1) * 256;
+ default:
+ return true;
}
+}
+
+void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
+ const uint8 *map = mapFeatureAt(cellX, cellY, direction);
+ if (!map || map[0] == kWallFeatureNone || !isWallFeatureFacingCamera(cellX, cellY, direction))
+ return;
float corners[4][3];
getWallFace3D(cellX, cellY, direction, corners);
@@ -649,6 +644,20 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
_gfx->setStippleData(nullptr);
};
+ auto fillStairwellSides = [&](const float farC[4][3], float nearD, float farD, float bottomV, float topV) {
+ if (!macMode && _wireframe)
+ return;
+ const float sideV[4] = {topV, bottomV, bottomV, topV};
+ const float sideD[4] = {nearD, nearD, farD, farD};
+ const float leftU[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ const float rightU[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+ _gfx->setStippleData(nullptr);
+ _gfx->setWireframe(false);
+ recessQuad(corners, farC, leftU, sideV, sideD, 4, wallFeatureFill);
+ recessQuad(corners, farC, rightU, sideV, sideD, 4, wallFeatureFill);
+ _gfx->setWireframe(true, wallFeatureFill);
+ };
+
switch (map[0]) {
case kWallFeatureDoor: {
const uint32 doorColor = macColors ? (uint32)0xFF000000 :
@@ -813,7 +822,11 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
hgt[i] = (float)(i + 1) / 8.0f;
}
- // ColorWall(), now the back of the hole.
+ // The original ColorWall() hid the corridor behind the entire opening.
+ // A recessed staircase also needs opaque sides to enclose the well.
+ fillStairwellSides(farC, 0.0f, 1.0f, 0.0f, 1.0f);
+
+ // Back of the well.
if (isMacRenderMode()) {
const float uw[4] = {0.0f, 1.0f, 1.0f, 0.0f};
const float vw[4] = {0.0f, 0.0f, 1.0f, 1.0f};
@@ -904,22 +917,13 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
hgt[i] = -(float)(i + 1) / 32.0f;
}
- // Fill the shaft sides without framing each panel, so their shared edges
- // form one continuous wall rather than a row of transparent-looking bars.
- if (isMacRenderMode() || !_wireframe) {
- _gfx->setStippleData(nullptr);
- _gfx->setWireframe(false);
- for (int i = 1; i <= 7; i++) {
- const float nearD = dep[i - 1];
- const float farD = (i < 7) ? dep[i] : 1.0f;
- const float bottomV = hgt[i - 1];
- const float sideV[4] = {0.0f, bottomV, bottomV, 0.0f};
- const float sideD[4] = {nearD, nearD, farD, farD};
- const float leftU[4] = {0.0f, 0.0f, 0.0f, 0.0f};
- const float rightU[4] = {1.0f, 1.0f, 1.0f, 1.0f};
- recessQuad(corners, farC, leftU, sideV, sideD, 4, wallFeatureFill);
- recessQuad(corners, farC, rightU, sideV, sideD, 4, wallFeatureFill);
- }
+ // Enclose the well above the floor, then extend its sides down to each
+ // tread. Leave the panels unframed so their shared edges stay invisible.
+ fillStairwellSides(farC, 0.0f, 1.0f, 0.0f, 1.0f);
+ for (int i = 1; i <= 7; i++) {
+ const float nearD = dep[i - 1];
+ const float farD = (i < 7) ? dep[i] : 1.0f;
+ fillStairwellSides(farC, nearD, farD, hgt[i - 1], 0.0f);
}
if (isMacRenderMode()) {
Commit: c0b89770e8e82a50dda9300920788de45928f638
https://github.com/scummvm/scummvm/commit/c0b89770e8e82a50dda9300920788de45928f638
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-25T13:34:53+02:00
Commit Message:
COLONY: new command and adjusted door behavior when power is off
Changed paths:
engines/colony/colony.h
engines/colony/console.cpp
engines/colony/console.h
engines/colony/map.cpp
engines/colony/movement.cpp
engines/colony/render.cpp
engines/colony/render_features.cpp
engines/colony/savegame.cpp
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 7c19b740675..6b54c6e59b5 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -390,7 +390,7 @@ struct PassPatch {
uint8 ang;
};
-// Per-level persistence: wall state changes (airlock locks) and visit flags.
+// Per-level persistence: door and airlock states, and visit flags.
struct LevelData {
uint8 visit;
uint8 queen;
@@ -399,6 +399,7 @@ struct LevelData {
uint8 size; // number of saved wall changes (max 10)
uint8 location[10][3]; // [x, y, direction] of each changed wall
uint8 data[10][5]; // saved wall feature bytes (5 per location)
+ uint8 openDoors[31][31]; // direction bits for open ordinary doors
};
struct MacColor {
@@ -788,6 +789,7 @@ private:
void resetObjectSlot(int slot, int type, int xloc, int yloc, uint8 ang);
bool createObject(int type, int xloc, int yloc, uint8 ang);
void saveLevelState();
+ void saveOpenDoors();
void doPatch();
void saveWall(int x, int y, int direction);
void getWall();
diff --git a/engines/colony/console.cpp b/engines/colony/console.cpp
index a39cfc43ab1..873c42cc9b3 100644
--- a/engines/colony/console.cpp
+++ b/engines/colony/console.cpp
@@ -116,6 +116,7 @@ Debugger::Debugger(ColonyEngine *vm) : GUI::Debugger(), _vm(vm) {
registerCmd("goals", WRAP_METHOD(Debugger, cmdGoals));
registerCmd("win", WRAP_METHOD(Debugger, cmdWin));
registerCmd("robots", WRAP_METHOD(Debugger, cmdRobots));
+ registerCmd("eradicate", WRAP_METHOD(Debugger, cmdEradicate));
registerCmd("map", WRAP_METHOD(Debugger, cmdMap));
registerCmd("give", WRAP_METHOD(Debugger, cmdGive));
registerCmd("power", WRAP_METHOD(Debugger, cmdPower));
@@ -488,6 +489,52 @@ bool Debugger::cmdRobots(int argc, const char **argv) {
return true;
}
+bool Debugger::cmdEradicate(int argc, const char **argv) {
+ if (argc != 1) {
+ debugPrintf("Usage: eradicate\n");
+ debugPrintf("Kills all monsters and eggs on every level.\n");
+ return true;
+ }
+
+ auto isCreature = [this](uint objectNum) {
+ if (objectNum == 0 || objectNum == kMeNum || objectNum > _vm->_objects.size())
+ return false;
+ const int type = _vm->_objects[objectNum - 1].type;
+ return type >= kRobEye && type <= kRobSnoop;
+ };
+
+ for (uint i = 0; i < _vm->_objects.size(); i++) {
+ if (isCreature(i + 1)) {
+ _vm->_objects[i].alive = 0;
+ _vm->_objects[i].visible = 0;
+ }
+ }
+ for (int x = 0; x < 32; x++) {
+ for (int y = 0; y < 32; y++) {
+ if (isCreature(_vm->_robotArray[x][y]))
+ _vm->_robotArray[x][y] = 0;
+ if (isCreature(_vm->_foodArray[x][y]))
+ _vm->_foodArray[x][y] = 0;
+ }
+ }
+ if (isCreature(_vm->_bumpedObject))
+ _vm->_bumpedObject = 0;
+ _vm->_insight = false;
+ _vm->_allGrow = false;
+
+ // Restore empty populations on later visits, including the first visit to
+ // a level. Keep the saved wall and airlock state in each LevelData intact.
+ for (uint i = 0; i < ARRAYSIZE(_vm->_levelData); i++) {
+ LevelData &ld = _vm->_levelData[i];
+ ld.visit = 1;
+ ld.queen = 0;
+ memset(ld.object, 0, sizeof(ld.object));
+ }
+
+ debugPrintf("Eradicated all monsters and eggs from all levels.\n");
+ return true;
+}
+
bool Debugger::cmdMap(int argc, const char **argv) {
int cx = _vm->_me.xindex;
int cy = _vm->_me.yindex;
diff --git a/engines/colony/console.h b/engines/colony/console.h
index b7cb9af782c..ea1a95b96e1 100644
--- a/engines/colony/console.h
+++ b/engines/colony/console.h
@@ -50,6 +50,7 @@ private:
bool cmdGoals(int argc, const char **argv);
bool cmdWin(int argc, const char **argv);
bool cmdRobots(int argc, const char **argv);
+ bool cmdEradicate(int argc, const char **argv);
bool cmdMap(int argc, const char **argv);
bool cmdGive(int argc, const char **argv);
bool cmdPower(int argc, const char **argv);
diff --git a/engines/colony/map.cpp b/engines/colony/map.cpp
index 9b68d0bcbb9..69e66fd66f7 100644
--- a/engines/colony/map.cpp
+++ b/engines/colony/map.cpp
@@ -171,7 +171,7 @@ void ColonyEngine::loadMap(int mnum) {
_coreIndex = (mnum == 1) ? 0 : 1;
_me.type = kMeNum;
- getWall(); // restore saved wall state changes (airlocks)
+ getWall(); // restore saved door and airlock states
doPatch(); // apply object relocations from patch table
initRobots(); // spawn robot objects for this level
@@ -357,11 +357,37 @@ void ColonyEngine::saveWall(int x, int y, int direction) {
ld.size++;
}
-// PATCH.C: getwall() restore saved wall bytes into _mapData after level load.
+void ColonyEngine::saveOpenDoors() {
+ if (_level < 1 || _level > 8)
+ return;
+
+ // Ordinary doors stay open after passage. Keep their state separately from
+ // the original ten-entry wall patch table, which only had room for airlocks.
+ LevelData &ld = _levelData[_level - 1];
+ for (int x = 0; x < 31; x++) {
+ for (int y = 0; y < 31; y++) {
+ ld.openDoors[x][y] = 0;
+ for (int dir = 0; dir < 4; dir++) {
+ if (_mapData[x][y][dir][0] == kWallFeatureDoor && _mapData[x][y][dir][1] == 0)
+ ld.openDoors[x][y] |= 1 << dir;
+ }
+ }
+ }
+}
+
+// Restore saved door states and the original PATCH.C wall patches after loading a level.
void ColonyEngine::getWall() {
if (_level < 1 || _level > 8)
return;
const LevelData &ld = _levelData[_level - 1];
+ for (int x = 0; x < 31; x++) {
+ for (int y = 0; y < 31; y++) {
+ for (int dir = 0; dir < 4; dir++) {
+ if ((ld.openDoors[x][y] & (1 << dir)) && _mapData[x][y][dir][0] == kWallFeatureDoor)
+ _mapData[x][y][dir][1] = 0;
+ }
+ }
+ }
for (int i = 0; i < ld.size; i++) {
int x = ld.location[i][0];
int y = ld.location[i][1];
diff --git a/engines/colony/movement.cpp b/engines/colony/movement.cpp
index ee2108f0ab0..0d9c6fcff0e 100644
--- a/engines/colony/movement.cpp
+++ b/engines/colony/movement.cpp
@@ -704,6 +704,9 @@ bool ColonyEngine::setDoorState(int x, int y, int direction, int state) {
_mapData[nx][ny][opposite][1] = (uint8)state;
}
+ if (wallType == kWallFeatureDoor)
+ saveOpenDoors();
+
// Persist airlock state changes across level loads
if (wallType == kWallFeatureAirlock) {
if (oldState != state && _level >= 1 && _level <= 8) {
diff --git a/engines/colony/render.cpp b/engines/colony/render.cpp
index 33223e58624..d4e2b3cfa0a 100644
--- a/engines/colony/render.cpp
+++ b/engines/colony/render.cpp
@@ -447,7 +447,7 @@ bool ColonyEngine::isVisibleRecessFeature(int x, int y, int direction) const {
// Only remove a wall when drawWallFeatures3D() will replace it with a well.
// Bit 0x01 spans (x,y-1)/(x,y); bit 0x02 spans (x-1,y)/(x,y).
bool ColonyEngine::wallSegmentIsOpenWell(int x, int y, uint8 bit) const {
- if (_corePower[_coreIndex] == 0)
+ if (!isMacColorMode() && _corePower[_coreIndex] == 0)
return false;
if (bit == 0x01)
diff --git a/engines/colony/render_features.cpp b/engines/colony/render_features.cpp
index 4e863c7545e..140e250ec21 100644
--- a/engines/colony/render_features.cpp
+++ b/engines/colony/render_features.cpp
@@ -189,6 +189,12 @@ void ColonyEngine::clipToWallFace(const float corners[4][3]) {
void ColonyEngine::macFillRecess(const float nearC[4][3], const float farC[4][3],
const float *u, const float *v, const float *d, int count, int macIdx, bool macColors) {
if (macColors) {
+ if (_corePower[_coreIndex] == 0) {
+ _gfx->setWireframe(true, 0);
+ recessQuad(nearC, farC, u, v, d, count, 0);
+ return;
+ }
+
uint32 fg = packMacColor(_macColors[macIdx].fg);
uint32 bg = packMacColor(_macColors[macIdx].bg);
const byte *stipple = setupMacPattern(_gfx, _macColors[macIdx].pattern, fg, bg);
@@ -208,7 +214,6 @@ void ColonyEngine::wallLine(const float corners[4][3], float u1, float v1, float
float p1[3], p2[3];
wallPoint(corners, u1, v1, p1);
wallPoint(corners, u2, v2, p2);
- // We assume this is only called when lit (handled in drawWallFeatures3D)
_gfx->draw3DLine(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2], color);
}
@@ -370,7 +375,8 @@ void ColonyEngine::wallChar(const float corners[4][3], uint8 cnum) {
const bool macMode = isMacRenderMode();
const bool macColors = isMacColorMode();
- const uint32 fillColor = macColors ? packMacColor(_macColors[8 + _level - 1].bg) : 0;
+ const bool lit = (_corePower[_coreIndex] > 0);
+ const uint32 fillColor = macColors && lit ? packMacColor(_macColors[8 + _level - 1].bg) : 0;
const uint32 lineColor = macColors ? (uint32)0xFF000000 : 0;
auto drawFilledCharPolygon = [&](const float *u, const float *v, int count) {
@@ -471,8 +477,8 @@ void ColonyEngine::wallChar(const float corners[4][3], uint8 cnum) {
if (macMode) {
const uint32 wallFill = macColors
- ? packMacColor(_macColors[8 + _level - 1].fg)
- : (uint32)255;
+ ? packMacColor(lit ? _macColors[8 + _level - 1].fg : _macColors[6].bg)
+ : (lit ? 255u : 0u);
_gfx->setWireframe(true, wallFill);
}
}
@@ -515,6 +521,12 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
// Helper lambda: draw a filled hole polygon with the platform material.
auto drawHolePoly = [&](const float *u, const float *v, int cnt, int macIdx) {
if (macColors) {
+ if (!lit) {
+ _gfx->setWireframe(true, 0);
+ wallPolygon(corners, u, v, cnt, 0);
+ return;
+ }
+
uint32 fg = packMacColor(_macColors[macIdx].fg);
uint32 bg = packMacColor(_macColors[macIdx].bg);
int pat = _macColors[macIdx].pattern;
@@ -576,8 +588,8 @@ void ColonyEngine::drawCellFeature3D(int cellX, int cellY) {
_gfx->setStippleData(nullptr);
const uint32 wallFill = macColors
- ? packMacColor(_macColors[8 + _level - 1].fg)
- : (macMode ? 255u : 7u);
+ ? packMacColor(lit ? _macColors[8 + _level - 1].fg : _macColors[6].bg)
+ : (lit ? (macMode ? 255u : 7u) : 0u);
_gfx->setWireframe(true, wallFill);
}
@@ -617,11 +629,18 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
? packMacColor(lit ? _macColors[8 + _level - 1].fg : _macColors[6].bg)
: (lit ? (macMode ? 255u : 7u) : 0u);
- // Wall faces are already filled with level-specific color (c_char0+level-1.fg)
- // by the wall grid in renderCorridor3D(). Features are drawn on top.
+ // Wall faces are already filled by the wall grid in renderCorridor3D().
+ // Features are drawn on top, including silhouettes when the power is off.
// Helper lambda: Mac color fill for a wall feature polygon
auto macFillPoly = [&](const float *u, const float *v, int cnt, int macIdx) {
+ // Mac SuperPoly() fills features with black when the power is off.
+ if (!lit) {
+ _gfx->setWireframe(true, 0);
+ wallPolygon(corners, u, v, cnt, 0);
+ return;
+ }
+
uint32 fg = packMacColor(_macColors[macIdx].fg);
uint32 bg = packMacColor(_macColors[macIdx].bg);
const byte *stipple = setupMacPattern(_gfx, _macColors[macIdx].pattern, fg, bg);
@@ -1141,7 +1160,7 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
// BLACK: solid black fill
_gfx->setWireframe(true, (uint32)0xFF000000);
wallPolygon(corners, ub, vb4, 4, 0xFF000000);
- _gfx->setWireframe(true, packMacColor(_macColors[8 + _level - 1].fg));
+ _gfx->setWireframe(true, wallFeatureFill);
} else {
macFillPoly(ub, vb4, 4, 26 + val); // c_color0 + val
}
@@ -1234,7 +1253,8 @@ void ColonyEngine::drawWallFeature3D(int cellX, int cellY, int direction) {
}
void ColonyEngine::drawWallFeatures3D() {
- if (_corePower[_coreIndex] == 0)
+ // The Mac color version draws features as black silhouettes without power.
+ if (!isMacColorMode() && _corePower[_coreIndex] == 0)
return;
for (int y = 0; y < 31; y++) {
diff --git a/engines/colony/savegame.cpp b/engines/colony/savegame.cpp
index 93be2061633..09d332b4ab6 100644
--- a/engines/colony/savegame.cpp
+++ b/engines/colony/savegame.cpp
@@ -29,6 +29,7 @@
namespace Colony {
const uint32 kSaveVersion = 1;
+const uint32 kDoorStateTag = MKTAG('D', 'O', 'O', 'R');
const uint32 kMaxSaveObjects = 4096;
const uint32 kMaxSavePatches = 100;
@@ -201,10 +202,11 @@ void writeLevelData(Common::WriteStream *stream, const LevelData &levelData) {
for (int j = 0; j < 5; j++)
stream->writeByte(levelData.data[i][j]);
}
+ stream->writeUint32BE(kDoorStateTag);
+ stream->write(levelData.openDoors, sizeof(levelData.openDoors));
}
-LevelData readLevelData(Common::SeekableReadStream *stream) {
- LevelData levelData;
+bool readLevelData(Common::SeekableReadStream *stream, LevelData &levelData) {
levelData.visit = stream->readByte();
levelData.queen = stream->readByte();
for (int i = 0; i <= kBaseObject; i++)
@@ -219,7 +221,18 @@ LevelData readLevelData(Common::SeekableReadStream *stream) {
for (int j = 0; j < 5; j++)
levelData.data[i][j] = stream->readByte();
}
- return levelData;
+ // Door states are required. The tag prevents saves without this block from
+ // being decoded using the following level's data as door states.
+ if (stream->readUint32BE() != kDoorStateTag ||
+ stream->read(levelData.openDoors, sizeof(levelData.openDoors)) != sizeof(levelData.openDoors))
+ return false;
+ for (int x = 0; x < 31; x++) {
+ for (int y = 0; y < 31; y++) {
+ if (levelData.openDoors[x][y] & ~0x0F)
+ return false;
+ }
+ }
+ return !stream->err() && !stream->eos();
}
bool validateGridReferences(const uint8 grid[32][32], uint32 objectCount, bool allowPlayerMarker) {
@@ -459,8 +472,10 @@ Common::Error ColonyEngine::loadGameStream(Common::SeekableReadStream *stream) {
for (int i = 0; i < 3; i++)
_corePower[i] = stream->readSint32LE();
- for (uint i = 0; i < ARRAYSIZE(_levelData); i++)
- _levelData[i] = readLevelData(stream);
+ for (uint i = 0; i < ARRAYSIZE(_levelData); i++) {
+ if (!readLevelData(stream, _levelData[i]))
+ return makeCorruptSaveError(Common::String::format("missing or invalid door state data for level %u", i + 1).c_str());
+ }
const uint32 patchCount = stream->readUint32LE();
if (patchCount > kMaxSavePatches)
@@ -527,7 +542,7 @@ Common::Error ColonyEngine::loadGameStream(Common::SeekableReadStream *stream) {
}
}
- if (stream->err())
+ if (stream->err() || stream->eos())
return makeCorruptSaveError("stream read error while decoding save payload");
if (_coreIndex < 0 || _coreIndex > 1)
Commit: ba109d8eb27f0363c6bda7cc9df4785be5f9fc0c
https://github.com/scummvm/scummvm/commit/ba109d8eb27f0363c6bda7cc9df4785be5f9fc0c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-25T13:34:53+02:00
Commit Message:
COLONY: improved end game animations
Changed paths:
engines/colony/colony.h
engines/colony/intro.cpp
engines/colony/sound.cpp
engines/colony/sound.h
diff --git a/engines/colony/colony.h b/engines/colony/colony.h
index 6b54c6e59b5..decc2a5d8f2 100644
--- a/engines/colony/colony.h
+++ b/engines/colony/colony.h
@@ -914,6 +914,8 @@ private:
bool makeStars(const Common::Rect &r, int btn);
bool makeBlackHole();
bool makePlanet();
+ bool leavePlanet();
+ bool explodePlanet();
bool timeSquare(const Common::String &str, const Graphics::Font *macFont = nullptr, bool gameOver = false);
bool drawPict(int resID);
bool loadAnimation(const Common::String &name);
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index ba15bac6f9e..4d7063f2993 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -1053,6 +1053,218 @@ bool ColonyEngine::makePlanet() {
return false;
}
+// leaveplanet() and explodeplanet() retain their XOR drawing between frames.
+// Keep that image in software: an OpenGL back buffer need not retain its pixels
+// after copyToScreen(), and the explosion deliberately accumulates dot trails.
+class PlanetAnimation {
+public:
+ PlanetAnimation(int width, int height, const int *sint, const int *cost, Common::RandomSource &random)
+ : _surface(width, height, Graphics::PixelFormat::createFormatRGBA32()),
+ _sint(sint), _cost(cost), _center(width / 2, height / 2) {
+ const uint32 black = _surface.format.RGBToColor(0, 0, 0);
+ _color = _surface.format.RGBToColor(255, 255, 255);
+ _xorMask = _color ^ black;
+ _surface.clear(black);
+
+ for (int distance = 800; distance > 32; distance -= 16) {
+ const int32 radius = (160 * 128) / distance;
+ for (int i = 0; i < 4; i++) {
+ const int angle = random.getRandomNumber(255);
+ _stars[_remainingStars++] = Common::Point(
+ _center.x + ((radius * _sint[angle]) >> 7),
+ _center.y + ((radius * _cost[angle]) >> 7));
+ }
+ }
+ }
+
+ void revealStars(int count) {
+ // Pre-decrement and stop at zero: the original reads an uninitialized
+ // star first, then runs below zero at the end of takeoff.
+ while (count-- > 0 && _remainingStars > 0)
+ plot(_stars[--_remainingStars]);
+ }
+
+ void drawPlanet(int distance, bool erasePrevious) {
+ const int32 radius = (160 * 128) / distance;
+ const int sintheta = _sint[210];
+ const int costheta = _cost[210];
+ int point = 0;
+
+ for (int j = 0; j < 256; j += 16) {
+ for (int k = _phase; k < 128; k += 16, point++) {
+ if (erasePrevious && _visible[point])
+ plot(_points[point]);
+
+ const int x = (((radius * _sint[j]) >> 7) * _cost[k]) >> 7;
+ const int z = (((radius * _sint[j]) >> 7) * _sint[k]) >> 7;
+ _visible[point] = ((_cost[j] * sintheta + z * costheta) >> 7) >= 0;
+ if (_visible[point]) {
+ const int y = ((((radius * _cost[j]) >> 7) * costheta - z * sintheta) >> 7);
+ _points[point] = Common::Point(_center.x + x, _center.y + y);
+ plot(_points[point]);
+ }
+ }
+ }
+ _phase = (_phase + 1) % 16;
+ }
+
+ void invert() {
+ for (int y = 0; y < _surface.h; y++) {
+ uint32 *pixels = (uint32 *)_surface.getBasePtr(0, y);
+ for (int x = 0; x < _surface.w; x++)
+ pixels[x] ^= _xorMask;
+ }
+ }
+
+ void setXorMode(bool enable) { _xorMode = enable; }
+
+ void setColor(byte r, byte g, byte b) {
+ _color = _surface.format.RGBToColor(r, g, b);
+ }
+
+ void present(Renderer *gfx) {
+ gfx->setXorMode(false);
+ gfx->clear(gfx->black());
+ gfx->drawSurface(&_surface.rawSurface(), 0, 0);
+ gfx->copyToScreen();
+ }
+
+private:
+ void plot(const Common::Point &p) {
+ if (p.x < 0 || p.y < 0 || p.x >= _surface.w || p.y >= _surface.h)
+ return;
+
+ uint32 *pixel = (uint32 *)_surface.getBasePtr(p.x, p.y);
+ if (_xorMode)
+ *pixel ^= _xorMask;
+ else
+ *pixel = _color;
+ }
+
+ Graphics::ManagedSurface _surface;
+ const int *_sint;
+ const int *_cost;
+ Common::Point _center;
+ Common::Point _stars[((800 - 32) / 16) * 4];
+ Common::Point _points[(256 / 16) * (128 / 16)];
+ bool _visible[(256 / 16) * (128 / 16)] = {};
+ int _remainingStars = 0;
+ int _phase = 0;
+ uint32 _color;
+ uint32 _xorMask;
+ bool _xorMode = true;
+};
+
+bool ColonyEngine::leavePlanet() {
+ PlanetAnimation planet(_width, _height, _sint, _cost, _randomSource);
+ planet.drawPlanet(32, false);
+ planet.present(_gfx);
+
+ // intro.c / IBM_INTR.C leaveplanet(): rotate close to the surface, then
+ // recede into the starfield, reversing the introductory planet approach.
+ for (int frame = 0; frame < 25; frame++) {
+ if (checkSkipRequested())
+ return true;
+ planet.drawPlanet(32, true);
+ planet.present(_gfx);
+ _system->delayMillis(33);
+ }
+ for (int distance = 32; distance <= 800; distance += 16) {
+ if (checkSkipRequested())
+ return true;
+ planet.revealStars(4);
+ planet.drawPlanet(distance, true);
+ planet.present(_gfx);
+ _system->delayMillis(16);
+ }
+
+ if (getPlatform() == Common::kPlatformMacintosh) {
+ while (_sound->isPlaying()) {
+ if (checkSkipRequested())
+ return true;
+ planet.present(_gfx);
+ _system->delayMillis(10);
+ }
+ }
+ return false;
+}
+
+bool ColonyEngine::explodePlanet() {
+ const bool isMac = getPlatform() == Common::kPlatformMacintosh;
+ PlanetAnimation planet(_width, _height, _sint, _cost, _randomSource);
+ _sound->play(Sound::kPShot);
+ planet.revealStars(192);
+ planet.drawPlanet(800, false);
+ planet.present(_gfx);
+
+ // DOS rotates until PlanetShot finishes (100 frames without sound).
+ // Mac instead rotates for 30 frames, inverting the last eight of them.
+ const bool waitForShot = !isMac && _sound->isPlaying();
+ const int rotationFrames = isMac ? 30 : 100;
+ for (int frame = 0; waitForShot ? _sound->isPlaying() : frame < rotationFrames; frame++) {
+ if (checkSkipRequested())
+ return true;
+ if (isMac && frame > 21) {
+ planet.invert();
+ if (!_sound->isPlaying())
+ _sound->play(Sound::kExplode);
+ }
+ planet.drawPlanet(800, true);
+ planet.present(_gfx);
+ _system->delayMillis(33);
+ }
+
+ if (!isMac) {
+ _sound->play(Sound::kExplode);
+ const int flashes = _renderMode == Common::kRenderEGA ? 2 : 4;
+ for (int i = 0; i < flashes; i++) {
+ if (checkSkipRequested())
+ return true;
+ planet.invert();
+ planet.present(_gfx);
+ _system->delayMillis(50);
+ }
+ while (_sound->isPlaying()) {
+ if (checkSkipRequested())
+ return true;
+ planet.present(_gfx);
+ _system->delayMillis(10);
+ }
+ _sound->play(Sound::kExplode);
+ }
+
+ // Stop erasing the old points: the globe fills in and then bursts into
+ // expanding trails. Mac switches to patCopy and shades blue toward red;
+ // DOS keeps drawing the white points in XOR mode.
+ for (int frame = 0; frame < (isMac ? 15 : 25); frame++) {
+ if (checkSkipRequested())
+ return true;
+ if (isMac && !_sound->isPlaying())
+ _sound->play(Sound::kExplode);
+ planet.drawPlanet(800, false);
+ planet.present(_gfx);
+ _system->delayMillis(33);
+ }
+
+ if (isMac) {
+ _sound->play(Sound::kEnd);
+ planet.setXorMode(false);
+ }
+ uint16 red = 0, blue = 0xFFFF;
+ for (int distance = 800; distance > 32; distance -= 16) {
+ if (checkSkipRequested())
+ return true;
+ red += 1024;
+ blue -= 1024;
+ if (isMacColorMode())
+ planet.setColor(red >> 8, 0, blue >> 8);
+ planet.drawPlanet(distance, false);
+ planet.present(_gfx);
+ _system->delayMillis(16);
+ }
+ return false;
+}
+
bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *macFont, bool gameOver) {
// Original: TimeSquare() in intro.c
// Mac and DOS use different presentation here. DOS is a monochrome/gray
@@ -1423,17 +1635,19 @@ void ColonyEngine::takeOff() {
_centerY = _height / 2;
debugC(1, kColonyDebugUI, "takeOff()");
+ const bool cursorWasVisible = CursorMan.isVisible();
+ CursorMan.showMouse(false);
_sound->stop();
_gfx->clear(_gfx->black());
_gfx->copyToScreen();
if (getPlatform() == Common::kPlatformMacintosh)
- _sound->play(Sound::kMars);
+ _sound->play(Sound::kSwish);
else
_sound->play(Sound::kStars1);
- makeStars(_screenR, 0);
+ leavePlanet();
_sound->stop();
_gfx->clear(_gfx->black());
@@ -1443,6 +1657,7 @@ void ColonyEngine::takeOff() {
_clip = savedClip;
_centerX = savedCenterX;
_centerY = savedCenterY;
+ CursorMan.showMouse(cursorWasVisible);
}
// Touching the monolith (the SCREEN object) blacks the screen out and runs the
@@ -1516,7 +1731,7 @@ void ColonyEngine::gameOver(bool kill, int savedCryos) {
_mouseLocked = false;
_system->lockMouse(false);
CursorMan.setDefaultArrowCursor(true);
- CursorMan.showMouse(true);
+ CursorMan.showMouse(false);
int textEntry;
@@ -1532,23 +1747,22 @@ void ColonyEngine::gameOver(bool kill, int savedCryos) {
_gfx->copyToScreen();
if (kill) {
- _sound->play(Sound::kPShot);
- makeStars(_screenR, 0);
- _sound->stop();
- _sound->play(Sound::kExplode);
- for (int i = 0; i < 4; i++) {
- _gfx->clear((i & 1) ? _gfx->black() : _gfx->white());
- _gfx->copyToScreen();
- _system->delayMillis(50);
- }
- } else {
+ explodePlanet();
+ _gfx->clear(_gfx->black());
+ _gfx->copyToScreen();
+ } else if (getPlatform() == Common::kPlatformMacintosh) {
+ _sound->play(Sound::kSwish);
+ }
+ if (getPlatform() != Common::kPlatformMacintosh)
_sound->play(Sound::kStars4);
- makeStars(_screenR, 0);
+ makeStars(_screenR, 0);
+ // Mac lets the ending sound finish over the text; DOS stops Stars4 here.
+ if (getPlatform() != Common::kPlatformMacintosh)
_sound->stop();
- }
_gfx->clear(_gfx->black());
_gfx->copyToScreen();
+ CursorMan.showMouse(true);
doText(textEntry, 2);
auto playFinalExplosion = [&]() {
diff --git a/engines/colony/sound.cpp b/engines/colony/sound.cpp
index 85f3cc5b8ba..0356757541c 100644
--- a/engines/colony/sound.cpp
+++ b/engines/colony/sound.cpp
@@ -340,6 +340,7 @@ void Sound::playPCSpeaker(int soundID) {
bool Sound::playMacSound(int soundID, bool loop) {
// Primary resource IDs from original sound.c
int resID = -1;
+ int sampleRate = 11127;
switch (soundID) {
case kKlaxon: resID = 27317; break;
case kAirlock: resID = 5141; break;
@@ -372,10 +373,14 @@ bool Sound::playMacSound(int soundID, bool loop) {
case kMars: resID = 23390; break;
case kBeamMe: resID = 5342; break;
case kDave: resID = 13651; break; // DAVE (the monolith's "full of stars" clip)
+ // LoadSound(SWISH, 5) and LoadSound(END, 4): StartSound's free-form
+ // synthesizer plays at 22254 Hz divided by the requested speed.
+ case kSwish: resID = 7089; sampleRate = 22254 / 5; break;
+ case kEnd: resID = 18282; sampleRate = 22254 / 4; break;
default: break;
}
- if (resID != -1 && playResource(resID, loop))
+ if (resID != -1 && playResource(resID, loop, sampleRate))
return true;
// Fallback resource IDs for sounds missing from this binary version.
@@ -396,7 +401,7 @@ bool Sound::playMacSound(int soundID, bool loop) {
return false;
}
-bool Sound::playResource(int resID, bool loop) {
+bool Sound::playResource(int resID, bool loop, int sampleRate) {
Common::SeekableReadStream *snd = nullptr;
// Search Zounds first (has most sounds)
@@ -422,7 +427,7 @@ bool Sound::playResource(int resID, bool loop) {
snd->read(data, dataSize);
delete snd;
- Audio::RewindableAudioStream *raw = Audio::makeRawStream(data, dataSize, 11127, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
+ Audio::RewindableAudioStream *raw = Audio::makeRawStream(data, dataSize, sampleRate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
Audio::AudioStream *stream = loop ? Audio::makeLoopingAudioStream(raw, 0) : raw;
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, stream);
return true;
diff --git a/engines/colony/sound.h b/engines/colony/sound.h
index 60e4ec147f4..56f44fe98ba 100644
--- a/engines/colony/sound.h
+++ b/engines/colony/sound.h
@@ -82,7 +82,9 @@ public:
kBath,
kMars,
kBeamMe,
- kDave
+ kDave,
+ kSwish,
+ kEnd
};
private:
@@ -94,7 +96,7 @@ private:
void playPCSpeaker(int soundID);
bool playMacSound(int soundID, bool loop);
- bool playResource(int resID, bool loop);
+ bool playResource(int resID, bool loop, int sampleRate = 11127);
};
} // End of namespace Colony
Commit: 4fa1ca2ea34e8bc7274081e82b601af29ca412ac
https://github.com/scummvm/scummvm/commit/4fa1ca2ea34e8bc7274081e82b601af29ca412ac
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-25T13:34:53+02:00
Commit Message:
COLONY: adjust text scrolling speed in intro
Changed paths:
engines/colony/intro.cpp
diff --git a/engines/colony/intro.cpp b/engines/colony/intro.cpp
index 4d7063f2993..a3692482499 100644
--- a/engines/colony/intro.cpp
+++ b/engines/colony/intro.cpp
@@ -1329,14 +1329,16 @@ bool ColonyEngine::timeSquare(const Common::String &str, const Graphics::Font *m
_gfx->copyToScreen();
// Phase 1: Scroll text in from the right to center.
- // DOS uses 16-pixel blits of a black text box; Mac scrolls smoothly.
+ // Original steps are one pixel on Mac and 16 on DOS. Double the intro's
+ // distance per frame so it also scrolls twice as fast with VSync enabled.
int targetX = (_width - swidth) / 2;
const int startX = macStyle ? _width : (_width + 16);
- const int stepX = macStyle ? 1 : 16;
+ const int stepX = (macStyle ? 1 : 16) * (gameOver ? 1 : 2);
const int endX = macStyle ? -swidth : (-swidth - 16);
const uint32 scrollDelayMs = macStyle ? 8 : (1000 / 60);
- for (int x = startX; x > targetX; x -= stepX) {
+ for (int x = startX; x > targetX;) {
+ x = MAX(targetX, x - stepX);
_gfx->fillRect(textBand, 0);
_gfx->drawString(font, str, x, centery + 2, textIndex, Graphics::kTextAlignLeft);
_gfx->copyToScreen();
More information about the Scummvm-git-logs
mailing list