[Scummvm-git-logs] scummvm master -> f43ebd3a88d0b9b6b91f0ca080f0e914cef522bb
sev-
noreply at scummvm.org
Sun Jun 21 22:39:01 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:
2bbb9d16a7 CHAMBER: Seed table RNG offset via Common::RandomSource
302bc60901 CHAMBER: Fix EGA MURSM sprite assembly squashing The Wall doors
f160896826 CHAMBER: Fix EGA endgame saucer animation and THE END screen
7e61d411d3 CHAMBER: Remove debug zone number from banner and hide cursor during text/room transitions
f43ebd3a88 CHAMBER: Add EGA zone transitions (Ring/passage spiral, lift, dot, zoom)
Commit: 2bbb9d16a7e9a4f494abf22c2f6628fa0c2cf5fb
https://github.com/scummvm/scummvm/commit/2bbb9d16a7e9a4f494abf22c2f6628fa0c2cf5fb
Author: Ion Andrei Cristian (lecturatul2017 at gmail.com)
Date: 2026-06-22T00:38:55+02:00
Commit Message:
CHAMBER: Seed table RNG offset via Common::RandomSource
Derive rand_seed from the engine's Common::RandomSource instead of
reading ConfMan/getMillis directly. RandomSource already honors the
"random_seed" config key for reproducible runs and otherwise seeds
from the time/date, so this drops the hand-rolled seeding while keeping
the original aleat_data[] table (and its exact distribution) intact.
Expose _rnd as public to match the other engine members reached through
g_vm, and drop the now-unused config-manager.h include in kult.cpp.
Changed paths:
engines/chamber/chamber.h
engines/chamber/kult.cpp
diff --git a/engines/chamber/chamber.h b/engines/chamber/chamber.h
index 2559d76b7d4..b23bf427327 100644
--- a/engines/chamber/chamber.h
+++ b/engines/chamber/chamber.h
@@ -47,11 +47,10 @@ enum CHAMBERActions {
};
class ChamberEngine : public Engine {
-private:
+public:
// We need random numbers
Common::RandomSource *_rnd;
-public:
ChamberEngine(OSystem *syst, const ADGameDescription *desc);
~ChamberEngine();
diff --git a/engines/chamber/kult.cpp b/engines/chamber/kult.cpp
index 58907294db0..ef110b3c611 100644
--- a/engines/chamber/kult.cpp
+++ b/engines/chamber/kult.cpp
@@ -19,7 +19,6 @@
*
*/
-#include "common/config-manager.h"
#include "common/error.h"
#include "common/system.h"
#include "engines/advancedDetector.h"
@@ -113,11 +112,12 @@ uint16 benchmarkCpu(void) {
void randomize(void) {
// Original read the low byte of the BIOS timer-tick count (int 0x1A) into
- // rand_seed. Use the host millisecond timer as an equivalent entropy source.
- if (ConfMan.hasKey("random_seed"))
- rand_seed = (byte)ConfMan.getInt("random_seed");
- else
- rand_seed = (byte)(g_system->getMillis());
+ // rand_seed, the starting offset into the fixed aleat_data[] table that
+ // getRand() walks. Keep that table (it reproduces the DOS game's exact
+ // random distribution) but derive the offset from ScummVM's RandomSource:
+ // its constructor already honors the "random_seed" config key for
+ // reproducible runs and otherwise seeds from the time/date.
+ rand_seed = (byte)g_vm->_rnd->getRandomNumber(255);
getRand();
}
Commit: 302bc609013480a937a3e156124700f01f6dd574
https://github.com/scummvm/scummvm/commit/302bc609013480a937a3e156124700f01f6dd574
Author: Ion Andrei Cristian (lecturatul2017 at gmail.com)
Date: 2026-06-22T00:38:55+02:00
Commit Message:
CHAMBER: Fix EGA MURSM sprite assembly squashing The Wall doors
loadMursmSprite placed each door sub-sprite at half spacing on both
axes (row = cgaOfs/40, col = (cgaOfs%40)/2), collapsing the 4x2 grid
into the top-left quarter and leaving the rest of the gate black.
Mirror the CGA layout (byte offset in a 20-byte-pitch buffer):
row = cgaOfs/20, col = cgaOfs%20. Fixes the black centre on the
closed/half-closed wall and the open/close animation frames.
Changed paths:
engines/chamber/room.cpp
diff --git a/engines/chamber/room.cpp b/engines/chamber/room.cpp
index c72977ba08e..3f79018456c 100644
--- a/engines/chamber/room.cpp
+++ b/engines/chamber/room.cpp
@@ -1573,11 +1573,13 @@ byte *loadMursmSprite(byte index) {
byte sprIdx = *pinfo++;
uint16 flags = *pinfo++;
flags |= (*pinfo++) << 8;
- uint16 cgaOfs = flags & 0x3FFF; /* offset in CGA flat buffer (pitch=20, 2 bytes/unit) */
+ uint16 cgaOfs = flags & 0x3FFF; /* byte offset into the CGA flat buffer (pitch = 20 bytes = 80 px) */
- /* Convert CGA flat offset â EGA flat offset */
- uint16 row = cgaOfs / (20 * 2); /* 20 CGA bytes à 2 bytes/unit = 40 bytes/row */
- uint16 colCga = (cgaOfs % (20 * 2)) / 2; /* column in CGA bytes */
+ /* Convert CGA flat offset â EGA flat offset. Mirror the CGA path,
+ which places the sub-sprite at byte cgaOfs in a 20-byte-pitch
+ buffer: row = cgaOfs / 20, column = cgaOfs % 20 CGA bytes (Ã 4 px). */
+ uint16 row = cgaOfs / 20;
+ uint16 colCga = cgaOfs % 20;
uint16 egaBufOfs = row * egaPitch + colCga * 4;
Graphics::Surface *surf = ega_puzzl_res->getSprite(sprIdx & 0x7F);
Commit: f16089682650d894ba7657c414d5cdd348416042
https://github.com/scummvm/scummvm/commit/f16089682650d894ba7657c414d5cdd348416042
Author: Ion Andrei Cristian (lecturatul2017 at gmail.com)
Date: 2026-06-22T00:38:55+02:00
Commit Message:
CHAMBER: Fix EGA endgame saucer animation and THE END screen
The EGA build crashed with a divide-by-zero (SIGFPE) at the winning end
sequence, the flying-saucer cutscene flew off-screen instead of receding,
and THE END logo was clipped or never drawn. Fix the EGA-specific paths:
- Add a real nearest-neighbour scaler (ega_scaleImage) and route zoomImage /
zoomInplaceXY through it. The EGA versions were non-scaling stubs that
ignored the target dimensions, so AnimSaucer redrew the saucer at native
size every frame instead of shrinking it.
- Treat the saucer path x coordinate from SOUCO.BIN as a raw pixel column,
not a 4-pixel-byte column (drop the *4). Every frame after the first was
landing far off-screen to the right, so the saucer appeared to shoot right
and vanish with no ascent.
- Sample the source with srcW/dstW rather than (srcW-1)/dstW so a 1:1 draw is
the identity map. The old form dropped the final source column, clipping the
right stroke of the 'D' in THE END.
- Add the missing pers_frames[9] = {9,11,0,0,0,0} end-logo frame. Its absence
made the EGA portrait builder read a zero frame width and divide by it
(SIGFPE), and left the logo unbuilt. Also guard the divide defensively.
- Clear the whole back buffer (and, in EGA, the front buffer) at the start of
AnimSaucer. The original's 'sizeof - 2' left the bottom-right two pixels
uncleared; in EGA the scroll-reveal lifted them up the screen as a red dot
rising with the saucer.
Changed paths:
engines/chamber/ega.cpp
engines/chamber/portrait.cpp
engines/chamber/script.cpp
diff --git a/engines/chamber/ega.cpp b/engines/chamber/ega.cpp
index 98a093a0004..7c61d61a2cd 100644
--- a/engines/chamber/ega.cpp
+++ b/engines/chamber/ega.cpp
@@ -492,16 +492,74 @@ void EGARenderer::traceLine(uint16 sx, uint16 ex, uint16 sy, uint16 ey, byte *so
flushing the whole screen here turned each line into an updateScreen.*/
}
-void EGARenderer::zoomImage(byte *pixels, byte w, byte h, byte /*nw*/, byte /*nh*/, byte *target, uint16 ofs) {
- blitAndWait(pixels, w, w, h, target, ofs);
+// Nearest-neighbour scale an 8-bpp source (srcW x srcH pixels) into a
+// dstW x dstH pixel rect at (dstX, dstY). This is the 8-bpp equivalent of the
+// CGA cga_ZoomInplace math; the CGA partial-pixel/xbase packing logic collapses
+// to a plain sample-and-copy here since every EGA pixel is one byte. Every write
+// is clipped to the 320x200 screen so the saucer animation can never bleed past
+// the framebuffer bounds.
+static void ega_scaleImage(const byte *pixels, uint16 srcW, uint16 srcH,
+ int16 dstX, int16 dstY, uint16 dstW, uint16 dstH, byte *target) {
+ if (dstW == 0 || dstH == 0 || srcW == 0 || srcH == 0)
+ return;
+
+ // 8.8 fixed-point source advance per target pixel. Use srcW/dstW (not
+ // (srcW-1)/dstW): at a 1:1 ratio that yields the identity map so the final
+ // source column/row is preserved -- the -1 form dropped it, which clipped the
+ // last pixel column (visible as the missing stroke of the "D" in THE END).
+ uint32 xstep = ((uint32)srcW << 8) / dstW;
+ uint32 ystep = ((uint32)srcH << 8) / dstH;
+
+ uint32 yval = 0;
+ for (uint16 ty = 0; ty < dstH; ty++, yval += ystep) {
+ int16 py = dstY + ty;
+ if (py < 0 || py >= EGA_HEIGHT)
+ continue;
+ uint16 sy = yval >> 8;
+ if (sy >= srcH)
+ sy = srcH - 1;
+ const byte *srcRow = pixels + sy * srcW;
+ byte *dstRow = target + py * EGA_BYTES_PER_LINE;
+ uint32 xval = 0;
+ for (uint16 tx = 0; tx < dstW; tx++, xval += xstep) {
+ int16 px = dstX + tx;
+ if (px < 0 || px >= EGA_WIDTH)
+ continue;
+ uint16 sx = xval >> 8;
+ if (sx >= srcW)
+ sx = srcW - 1;
+ dstRow[px] = srcRow[sx];
+ }
+ }
+
+ if (target == SCREENBUFFER) {
+ // flush the clipped destination rect
+ int16 x0 = dstX < 0 ? 0 : dstX;
+ int16 y0 = dstY < 0 ? 0 : dstY;
+ int16 x1 = dstX + (int16)dstW;
+ int16 y1 = dstY + (int16)dstH;
+ if (x1 > EGA_WIDTH) x1 = EGA_WIDTH;
+ if (y1 > EGA_HEIGHT) y1 = EGA_HEIGHT;
+ if (x1 > x0 && y1 > y0)
+ g_vm->_renderer->blitToScreen(x0, y0, x1 - x0, y1 - y0);
+ }
+}
+
+void EGARenderer::zoomImage(byte *pixels, byte w, byte h, byte nw, byte nh, byte *target, uint16 ofs) {
+ // nw is the target width in 4-pixel bytes, nh the height in rows (see CGA zoomImage).
+ ega_scaleImage(pixels, w * 4, h, ofs % EGA_BYTES_PER_LINE, ofs / EGA_BYTES_PER_LINE, nw * 4, nh, target);
}
void EGARenderer::animZoomIn(byte *pixels, byte w, byte h, byte *target, uint16 ofs) {
blitAndWait(pixels, w, w, h, target, ofs);
}
-void EGARenderer::zoomInplaceXY(byte *pixels, byte w, byte h, byte /*nw*/, byte /*nh*/, uint16 x, uint16 y, byte *target) {
- blit(pixels, w, w, h, target, calcXY_p(x, y));
+void EGARenderer::zoomInplaceXY(byte *pixels, byte w, byte h, byte nw, byte nh, uint16 x, uint16 y, byte *target) {
+ // AnimSaucer's only caller passes the saucer path data from SOUCO.BIN, whose
+ // x/y/nw/nh are raw pixel coordinates (0..319), not 4-pixel-byte columns. So x
+ // is already a pixel column here -- pass it straight through (no *4), otherwise
+ // every frame after the first lands far off-screen to the right.
+ ega_scaleImage(pixels, w * 4, h, x, y, nw, nh, target);
}
void EGARenderer::drawSprite(byte *sprite, byte *screen, uint16 ofs) {
diff --git a/engines/chamber/portrait.cpp b/engines/chamber/portrait.cpp
index 8e56711ffc1..1c956b95811 100644
--- a/engines/chamber/portrait.cpp
+++ b/engines/chamber/portrait.cpp
@@ -70,7 +70,8 @@ persframe_t pers_frames[] = {
{47, 13, 0xFF, 0, 0xC0, 3},
{65, 18, 0xFF, 0xAA, 0xEA, 0xAB},
{38, 11, 0xFF, 0, 0xC0, 3},
- {27, 34, 0, 0, 0, 0}
+ {27, 34, 0, 0, 0, 0},
+ { 9, 11, 0, 0, 0, 0} /*end-screen logo frame (icon 167); missing entry caused a divide-by-zero in EGA*/
};
void makePortraitFrame(byte index, byte *target) {
@@ -205,6 +206,8 @@ static byte *ega_loadPortrait(byte **pinfo, byte *end) {
int16 spitch = surf->pitch;
uint16 cga_ofs = flags & 0x3FFF;
+ if (cur_frame_width == 0) /*degenerate frame: avoid the divide below*/
+ continue;
uint16 row = cga_ofs / cur_frame_width;
uint16 col_cga = cga_ofs % cur_frame_width;
byte *dst = sprit_load_buffer + 2 + 2 + row * frame_pw + col_cga * 4;
diff --git a/engines/chamber/script.cpp b/engines/chamber/script.cpp
index 9359788b66b..5ec0ba077ad 100644
--- a/engines/chamber/script.cpp
+++ b/engines/chamber/script.cpp
@@ -2875,7 +2875,17 @@ static void AnimSaucer(void) {
uint16 delay;
byte scroll_done = 0;
- memset(backbuffer, 0, sizeof(backbuffer) - 2); /*TODO: original bug?*/
+ /*Clear the whole buffer: the original's "- 2" left the bottom-right two
+ bytes (row 199, x=318..319) holding stale pixels. In EGA those are visible
+ and the scroll-reveal lifts them up the screen as a red dot rising with the
+ saucer, so clear all of it.*/
+ memset(backbuffer, 0, sizeof(backbuffer));
+ /*EGA: also blank the front buffer. The saucer animation only paints the
+ saucer region, so leftover sprites from the final game frame (player,
+ HUD pixels) would otherwise survive on the black backdrop right through to
+ THE END screen.*/
+ if (g_vm->_videoMode == Common::kRenderEGA)
+ memset(frontbuffer, 0, sizeof(SCREENBUFFER));
g_vm->_renderer->backBufferToRealFull();
g_vm->_renderer->colorSelect(0x30);
Commit: 7e61d411d343178250e273576755c851e8b1b2cf
https://github.com/scummvm/scummvm/commit/7e61d411d343178250e273576755c851e8b1b2cf
Author: Ion Andrei Cristian (lecturatul2017 at gmail.com)
Date: 2026-06-22T00:38:55+02:00
Commit Message:
CHAMBER: Remove debug zone number from banner and hide cursor during text/room transitions
Disable DEBUG_ZONE so the 3-digit zone index no longer prints on the
location banner. Hide the mouse pointer before text boxes/bubbles are
drawn and while a room's entry animation plays; the game loop re-shows
it once it waits for player input.
Changed paths:
engines/chamber/common.h
engines/chamber/cursor.cpp
engines/chamber/cursor.h
engines/chamber/dialog.cpp
engines/chamber/print.cpp
engines/chamber/room.cpp
diff --git a/engines/chamber/common.h b/engines/chamber/common.h
index a2314c736b9..eb9dbe66480 100644
--- a/engines/chamber/common.h
+++ b/engines/chamber/common.h
@@ -58,7 +58,7 @@ typedef struct rect_t {
#define DEBUG_SKIP_INTRO 0xFF
#endif
-#if 1
+#if 0
#define DEBUG_ZONE
#endif
diff --git a/engines/chamber/cursor.cpp b/engines/chamber/cursor.cpp
index 7474ed31785..38d0e6cd84b 100644
--- a/engines/chamber/cursor.cpp
+++ b/engines/chamber/cursor.cpp
@@ -153,6 +153,13 @@ Restore background pixels under cursor
void undrawCursor(byte *target) {
}
+/*
+Hide the system mouse pointer (used during non-interactive room transitions)
+*/
+void hideMouseCursor(void) {
+ CursorMan.showMouse(false);
+}
+
/*
Restore pixels under cursor and update cursor sprite
*/
diff --git a/engines/chamber/cursor.h b/engines/chamber/cursor.h
index 0a22c84a352..bf898096b74 100644
--- a/engines/chamber/cursor.h
+++ b/engines/chamber/cursor.h
@@ -52,6 +52,7 @@ void updateCursor(void);
void drawCursor(byte *target);
void undrawCursor(byte *target);
void updateUndrawCursor(byte *target);
+void hideMouseCursor(void);
} // End of namespace Chamber
diff --git a/engines/chamber/dialog.cpp b/engines/chamber/dialog.cpp
index 7235a716442..7b92359c6b8 100644
--- a/engines/chamber/dialog.cpp
+++ b/engines/chamber/dialog.cpp
@@ -20,6 +20,7 @@
*/
#include "common/system.h"
+#include "graphics/cursorman.h"
#include "chamber/chamber.h"
#include "chamber/common.h"
@@ -114,6 +115,10 @@ void drawPersonBubble(byte x, byte y, byte flags, byte *msg) {
byte w, h;
uint16 ww, nw;
+ // Hide the mouse pointer before the bubble is drawn, so it is gone from the
+ // moment the text appears. selectCursor() re-shows it when play resumes.
+ CursorMan.showMouse(false);
+
char_draw_max_width = flags & 0x1F;
char_xlat_table = chars_color_bonw;
diff --git a/engines/chamber/print.cpp b/engines/chamber/print.cpp
index e16ce3484e5..42fc5f9d632 100644
--- a/engines/chamber/print.cpp
+++ b/engines/chamber/print.cpp
@@ -19,6 +19,8 @@
*
*/
+#include "graphics/cursorman.h"
+
#include "chamber/chamber.h"
#include "chamber/common.h"
#include "chamber/cga.h"
@@ -198,6 +200,11 @@ void cga_DrawTextBox(byte *msg, byte *target) {
uint16 x, y, w, i;
uint16 ww, nw;
+ // Hide the mouse pointer before the text box is drawn, so it is gone from
+ // the moment the box appears (not only once promptWait() begins). The game
+ // loop's selectCursor() re-shows it when interactive play resumes.
+ CursorMan.showMouse(false);
+
char_xlat_table = chars_color_bonc;
if (g_vm->getLanguage() == Common::EN_USA) {
diff --git a/engines/chamber/room.cpp b/engines/chamber/room.cpp
index 3f79018456c..c0b39c7281b 100644
--- a/engines/chamber/room.cpp
+++ b/engines/chamber/room.cpp
@@ -1040,6 +1040,11 @@ void drawZoneSpots(void) {
}
void refreshZone(void) {
+ // Hide the mouse pointer while the new room is drawn and its entry
+ // animation plays. The game loop's selectCursor() re-shows it once it is
+ // waiting for player input again.
+ hideMouseCursor();
+
popDirtyRects(DirtyRectSprite);
popDirtyRects(DirtyRectBubble);
popDirtyRects(DirtyRectText);
Commit: f43ebd3a88d0b9b6b91f0ca080f0e914cef522bb
https://github.com/scummvm/scummvm/commit/f43ebd3a88d0b9b6b91f0ca080f0e914cef522bb
Author: Ion Andrei Cristian (lecturatul2017 at gmail.com)
Date: 2026-06-22T00:38:55+02:00
Commit Message:
CHAMBER: Add EGA zone transitions (Ring/passage spiral, lift, dot, zoom)
Implement the EGA renderer's stubbed transition effects so room changes
animate like CGA: the spiral background reveal when walking The Ring and
passages (gated by the previously-unused skip_zone_transition), plus the
lift wipes, dot dissolve and zoom-in reveals.
Changed paths:
engines/chamber/anim.cpp
engines/chamber/ega.cpp
engines/chamber/ega.h
engines/chamber/room.cpp
engines/chamber/script.cpp
diff --git a/engines/chamber/anim.cpp b/engines/chamber/anim.cpp
index 8ba0b2d9e9f..18a8108a2e3 100644
--- a/engines/chamber/anim.cpp
+++ b/engines/chamber/anim.cpp
@@ -116,6 +116,36 @@ void clipSprite(byte *x, byte *y, byte *sprw, byte *sprh, byte **sprite, int8 dx
void copyScreenBlockWithDotEffect(byte *source, byte x, byte y, byte width, byte height, byte *target) {
if (g_vm->_videoMode == Common::kRenderEGA) {
+ /* EGA: linear 1 byte/pixel. Reveal the block in the same scattered
+ ("dot dissolve") order as the CGA path, blitting periodically so the
+ transition is animated instead of an instant copy. */
+ uint16 xx = x * 4;
+ uint16 ww = width * 4;
+ uint32 end = (uint32)ww * height;
+ uint16 step = dot_effect_step ? dot_effect_step : 17;
+ uint32 offs = 0;
+ uint32 guard = 0;
+
+ if (target == SCREENBUFFER) {
+ do {
+ uint16 px = xx + offs % ww;
+ uint16 py = y + offs / ww;
+ uint16 ofs = py * EGA_BYTES_PER_LINE + px;
+ target[ofs] = source[ofs];
+
+ offs += step;
+ if (offs > end)
+ offs -= end;
+
+ /* blit roughly once per block-row's worth of dots */
+ if ((++guard % ww) == 0) {
+ g_vm->_renderer->blitToScreen(xx, y, ww, height);
+ waitVBlank();
+ }
+ } while (offs != 0 && guard <= end);
+ }
+
+ /* ensure the block is fully copied (the scatter may skip pixels) */
g_vm->_renderer->copyScreenBlock(source, width, height, target, g_vm->_renderer->calcXY_p(x, y));
return;
}
diff --git a/engines/chamber/ega.cpp b/engines/chamber/ega.cpp
index 7c61d61a2cd..d9a346e025a 100644
--- a/engines/chamber/ega.cpp
+++ b/engines/chamber/ega.cpp
@@ -65,6 +65,65 @@ void ega_drawBackground(byte *target) {
g_vm->_renderer->blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
}
+#define EGA_WIPE_BLOCK 20
+
+static int s_wipeBlocks;
+
+/*Reveal one clean-background block onto the screen; flush+throttle every few blocks*/
+static void egaRevealBlock(int bx, int by) {
+ int w = (bx + EGA_WIPE_BLOCK > EGA_WIDTH) ? (EGA_WIDTH - bx) : EGA_WIPE_BLOCK;
+ int h = (by + EGA_WIPE_BLOCK > EGA_HEIGHT) ? (EGA_HEIGHT - by) : EGA_WIPE_BLOCK;
+ if (w <= 0 || h <= 0)
+ return;
+ for (int i = 0; i < h; i++)
+ memcpy(SCREENBUFFER + (by + i) * EGA_BYTES_PER_LINE + bx,
+ ega_fond_clean + (by + i) * EGA_BYTES_PER_LINE + bx, w);
+ if (++s_wipeBlocks % 3 == 0) {
+ g_vm->_renderer->blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ waitVBlank();
+ }
+}
+
+void ega_zoneRevealWipe(void) {
+ // Original zone-change transition for The Ring / passages: first the empty
+ // background pattern spirals in over the old room (no room underneath), then
+ // the new room is shown. Here we spiral in the clean background, then blit the
+ // new room (already composited in the backbuffer) on top.
+ const int B = EGA_WIPE_BLOCK;
+ int left = 0, top = 0, right = EGA_WIDTH, bottom = EGA_HEIGHT;
+ s_wipeBlocks = 0;
+
+ while (left < right && top < bottom) {
+ for (int x = left; x < right; x += B) /*top row, L->R*/
+ egaRevealBlock(x, top);
+ top += B;
+ for (int y = top; y < bottom; y += B) /*right col, T->B*/
+ egaRevealBlock(right - B, y);
+ right -= B;
+ if (top < bottom) {
+ for (int x = right - B; x >= left; x -= B) /*bottom row, R->L*/
+ egaRevealBlock(x, bottom - B);
+ bottom -= B;
+ }
+ if (left < right) {
+ for (int y = bottom - B; y >= top; y -= B) /*left col, B->T*/
+ egaRevealBlock(left, y);
+ left += B;
+ }
+ }
+
+ // make sure the whole play area is the clean background (no room), then hold
+ // briefly so the empty background is visible before the room appears
+ memcpy(SCREENBUFFER, ega_fond_clean, EGA_SCREEN_SIZE);
+ g_vm->_renderer->blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ for (int i = 0; i < 8; i++)
+ waitVBlank();
+
+ // now show the new room (already composited in the backbuffer)
+ memcpy(SCREENBUFFER, ega_backbuffer, EGA_SCREEN_SIZE);
+ g_vm->_renderer->blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+}
+
Graphics::Surface *ega_loadFond(const char *filename) {
Common::File fd;
if (!fd.open(filename)) {
@@ -452,20 +511,83 @@ void EGARenderer::animLiftToRight(uint16 n, byte *pixels, uint16 pw, uint16 w, u
}
}
+/* EGA port of the CGA lift transitions: shift the block one line/column per
+ step, fill the freed edge from source (the new image), then blit+vblank so
+ the move is animated. ofs is a linear EGA offset; w is in CGA bytes, so a
+ block column is 4 EGA pixels wide. */
+
+/*Shift block n lines down, filling the freed top line from source*/
void EGARenderer::hideScreenBlockLiftToDown(uint16 n, byte *screen, byte *source, uint16 w, uint16 h, byte *target, uint16 ofs) {
- ega_CopyScreenBlock(source, w * 4, h + n, target, ofs);
+ uint16 ew = w * 4;
+ while (n--) {
+ uint16 sofs = ofs;
+ uint16 tofs = ofs + EGA_BYTES_PER_LINE;
+ for (uint16 i = 0; i < h; i++) {
+ memcpy(target + tofs, screen + sofs, ew);
+ tofs = sofs;
+ sofs -= EGA_BYTES_PER_LINE;
+ }
+ memcpy(target + tofs, source + tofs, ew);
+ if (screen == SCREENBUFFER)
+ blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ waitVBlank();
+ ofs += EGA_BYTES_PER_LINE;
+ }
}
+/*Shift block n lines up, filling the freed bottom line from source*/
void EGARenderer::hideScreenBlockLiftToUp(uint16 n, byte *screen, byte *source, uint16 w, uint16 h, byte *target, uint16 ofs) {
- ega_CopyScreenBlock(source, w * 4, h + n, target, ofs);
+ uint16 ew = w * 4;
+ while (n--) {
+ uint16 sofs = ofs;
+ uint16 tofs = ofs - EGA_BYTES_PER_LINE;
+ for (uint16 i = 0; i < h; i++) {
+ memcpy(target + tofs, screen + sofs, ew);
+ tofs = sofs;
+ sofs += EGA_BYTES_PER_LINE;
+ }
+ memcpy(target + tofs, source + tofs, ew);
+ if (screen == SCREENBUFFER)
+ blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ waitVBlank();
+ ofs -= EGA_BYTES_PER_LINE;
+ }
}
+/*Shift block n columns left, filling the freed right column from source*/
void EGARenderer::hideScreenBlockLiftToLeft(uint16 n, byte *screen, byte *source, uint16 w, uint16 h, byte *target, uint16 ofs) {
- ega_CopyScreenBlock(source, (w + n) * 4, h, target, ofs - n * 4);
+ uint16 ew = w * 4;
+ while (n--) {
+ uint16 sofs = ofs;
+ for (uint16 i = 0; i < h; i++) {
+ uint16 tofs = sofs - 4;
+ memmove(target + tofs, screen + sofs, ew);
+ memcpy(target + tofs + ew, source + tofs + ew, 4);
+ sofs += EGA_BYTES_PER_LINE;
+ }
+ if (screen == SCREENBUFFER)
+ blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ waitVBlank();
+ ofs -= 4;
+ }
}
+/*Shift block n columns right, filling the freed left column from source*/
void EGARenderer::hideScreenBlockLiftToRight(uint16 n, byte *screen, byte *source, uint16 w, uint16 h, byte *target, uint16 ofs) {
- ega_CopyScreenBlock(source, (w + n) * 4, h, target, ofs - w * 4);
+ uint16 ew = w * 4;
+ while (n--) {
+ uint16 sofs = ofs;
+ for (uint16 i = 0; i < h; i++) {
+ uint16 tofs = sofs + 4;
+ memmove(target + tofs - ew, screen + sofs - ew, ew);
+ memcpy(target + tofs - ew, source + tofs - ew, 4);
+ sofs += EGA_BYTES_PER_LINE;
+ }
+ if (screen == SCREENBUFFER)
+ blitToScreen(0, 0, EGA_WIDTH, EGA_HEIGHT);
+ waitVBlank();
+ ofs += 4;
+ }
}
void EGARenderer::hideShatterFall(byte *screen, byte *source, uint16 w, uint16 h, byte *target, uint16 ofs) {
@@ -551,6 +673,38 @@ void EGARenderer::zoomImage(byte *pixels, byte w, byte h, byte nw, byte nh, byte
}
void EGARenderer::animZoomIn(byte *pixels, byte w, byte h, byte *target, uint16 ofs) {
+ /* Progressive zoom-in reveal (CGA animZoomIn equivalent for EGA): draw the
+ image scaled from a small size up to full, growing out of its centre.
+ Each larger frame is centred and covers the previous one, so no per-step
+ restore is needed. zoomImage() draws and flushes the scaled rect. */
+ uint16 fw = (uint16)w * 4; /* final width in pixels */
+ uint16 fh = h; /* final height in rows */
+ uint16 ox = ofs % EGA_BYTES_PER_LINE;
+ uint16 oy = ofs / EGA_BYTES_PER_LINE;
+ uint16 cx = ox + fw / 2; /* zoom centre */
+ uint16 cy = oy + fh / 2;
+
+ uint16 maxside = (fw > fh) ? fw : fh;
+ uint16 nsteps = maxside / 2;
+ if (nsteps < 8)
+ nsteps = 8;
+ if (nsteps > 64)
+ nsteps = 64;
+
+ for (uint16 s = 1; s < nsteps; s++) {
+ byte nw = (byte)((uint16)w * s / nsteps);
+ byte nh = (byte)((uint16)h * s / nsteps);
+ if (nw < 1)
+ nw = 1;
+ if (nh < 1)
+ nh = 1;
+ uint16 tx = cx - (uint16)nw * 4 / 2;
+ uint16 ty = cy - (uint16)nh / 2;
+ zoomImage(pixels, w, h, nw, nh, target, ty * EGA_BYTES_PER_LINE + tx);
+ waitVBlank();
+ }
+
+ /* final full-size image */
blitAndWait(pixels, w, w, h, target, ofs);
}
diff --git a/engines/chamber/ega.h b/engines/chamber/ega.h
index 9d3d7f5050a..9516de42274 100644
--- a/engines/chamber/ega.h
+++ b/engines/chamber/ega.h
@@ -50,6 +50,10 @@ Graphics::Surface *ega_loadFond(const char *filename);
extern byte ega_fond_clean[EGA_SCREEN_SIZE];
void ega_drawBackground(byte *target);
+// --- zone-change transition ---
+// Reveal the new room (backbuffer) over the screen with a serpentine line wipe.
+void ega_zoneRevealWipe(void);
+
} // End of namespace Chamber
#endif // CHAMBER_EGA_H
diff --git a/engines/chamber/room.cpp b/engines/chamber/room.cpp
index c0b39c7281b..63cdfb33c2d 100644
--- a/engines/chamber/room.cpp
+++ b/engines/chamber/room.cpp
@@ -1039,6 +1039,23 @@ void drawZoneSpots(void) {
drawPersons();
}
+/*
+The line-wipe zone transition only plays while moving through the corridor-like
+areas: The Ring and the Passages. Normal room entries stay instant (or use their
+own scripted drawIcone effect).
+*/
+static bool zoneHasWalkTransition(byte area) {
+ switch (area) {
+ case kAreaTheRing1: case kAreaTheRing2: case kAreaTheRing3:
+ case kAreaTheRing4: case kAreaTheRing5: case kAreaTheRing6:
+ case kAreaPassage1: case kAreaPassage2: case kAreaPassage3:
+ case kAreaPassage4: case kAreaPassage5: case kAreaPassage6: case kAreaPassage7:
+ return true;
+ default:
+ return false;
+ }
+}
+
void refreshZone(void) {
// Hide the mouse pointer while the new room is drawn and its entry
// animation plays. The game loop's selectCursor() re-shows it once it is
@@ -1049,7 +1066,16 @@ void refreshZone(void) {
popDirtyRects(DirtyRectBubble);
popDirtyRects(DirtyRectText);
- g_vm->_renderer->backBufferToRealFull();
+ // Reveal the new room with an animated transition unless this change asked to
+ // skip it (e.g. entering through an already-animated door). skip_zone_transition
+ // is set by SCR_42_LoadZone; plain zone changes (walking around The Ring / through
+ // passages via SCR_36_ChangeZone) leave it 0 and so get the dot-dissolve reveal.
+ if (!skip_zone_transition && g_vm->_videoMode == Common::kRenderEGA
+ && zoneHasWalkTransition(script_byte_vars.zone_area)) {
+ ega_zoneRevealWipe();
+ } else {
+ g_vm->_renderer->backBufferToRealFull();
+ }
in_de_profundis = 0;
IFGM_StopSample();
diff --git a/engines/chamber/script.cpp b/engines/chamber/script.cpp
index 5ec0ba077ad..754faf0fc36 100644
--- a/engines/chamber/script.cpp
+++ b/engines/chamber/script.cpp
@@ -1109,7 +1109,13 @@ uint16 SCR_1B_HidePortraitLiftUp(void) {
}
if (g_vm->_videoMode == Common::kRenderEGA) {
- g_vm->_renderer->copyScreenBlock(backbuffer, width, height, SCREENBUFFER, offs);
+ offs = g_vm->_renderer->calcXY_p(x, y + 1);
+ while (--height)
+ g_vm->_renderer->hideScreenBlockLiftToUp(1, SCREENBUFFER, backbuffer, width, height, SCREENBUFFER, offs);
+ /*hide topmost line*/
+ offs -= EGA_BYTES_PER_LINE;
+ memcpy(SCREENBUFFER + offs, backbuffer + offs, width * 4);
+ g_vm->_renderer->blitToScreen(offs % EGA_BYTES_PER_LINE, offs / EGA_BYTES_PER_LINE, width * 4, 1);
return 0;
}
@@ -1150,7 +1156,13 @@ uint16 SCR_1C_HidePortraitLiftDown(void) {
}
if (g_vm->_videoMode == Common::kRenderEGA) {
- g_vm->_renderer->copyScreenBlock(backbuffer, width, height, SCREENBUFFER, offs);
+ offs = g_vm->_renderer->calcXY_p(x, y + height - 2);
+ while (--height)
+ g_vm->_renderer->hideScreenBlockLiftToDown(1, SCREENBUFFER, backbuffer, width, height, SCREENBUFFER, offs);
+ /*hide bottommost line*/
+ offs += EGA_BYTES_PER_LINE;
+ memcpy(SCREENBUFFER + offs, backbuffer + offs, width * 4);
+ g_vm->_renderer->blitToScreen(offs % EGA_BYTES_PER_LINE, offs / EGA_BYTES_PER_LINE, width * 4, 1);
return 0;
}
@@ -1612,6 +1624,8 @@ Draw new zone
*/
uint16 SCR_43_RefreshZone(void) {
script_ptr++;
+ // A standalone refresh (not a zone change) should just redraw instantly.
+ skip_zone_transition = 1;
refreshZone();
return 0;
}
More information about the Scummvm-git-logs
mailing list