[Scummvm-git-logs] scummvm master -> 084f0d6376b59d2d8aaf55b1f6390d88cbcc30a2
neuromancer
noreply at scummvm.org
Sat Jun 6 19:01:46 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
80a7a31357 SCUMM: RA2: small corruption fixed in some levels
4865d17161 SCUMM: RA2: fixed corruption from L2 to L3
084f0d6376 SCUMM: RA2: reduce oscilation when using the mouse as input
Commit: 80a7a313577987c3c5bd1b91522ecaf61abd9f70
https://github.com/scummvm/scummvm/commit/80a7a313577987c3c5bd1b91522ecaf61abd9f70
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-06T20:45:51+02:00
Commit Message:
SCUMM: RA2: small corruption fixed in some levels
Changed paths:
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/codec_ra2.cpp
engines/scumm/smush/rebel/codec_ra2.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 7b4781a1ffa..7b8cb21dee8 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -174,6 +174,8 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_pauseOverlayActive = false;
memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
+ memset(_rebelEmbeddedCodec45Palette, 0, sizeof(_rebelEmbeddedCodec45Palette));
+ memset(_rebelEmbeddedCodec45Lookup, 0, sizeof(_rebelEmbeddedCodec45Lookup));
_enemies.clear();
_rebelHandler = 0; // Not set yet - will be set by IACT opcode 6
@@ -1102,6 +1104,20 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
}
break;
+ case Common::KEYCODE_d:
+ // Debug shortcut: Shift+D forces the current gameplay section to end in death.
+ if (splayer &&
+ _gameState == kStateGameplay &&
+ _rebelHandler != 0 &&
+ event.kbd.hasFlags(Common::KBD_SHIFT)) {
+ _playerDamage = 255;
+ _playerShield = 0;
+ debug("Rebel2: Shift+D pressed - forcing player death");
+ _vm->_smushVideoShouldFinish = true;
+ return true; // Consume the event
+ }
+ break;
+
default:
break;
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 099add0d6cd..e8fe499b865 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -772,6 +772,8 @@ public:
};
EmbeddedSanFrame _rebelEmbeddedHud[16]; // HUD overlay slots (userId 0-15)
+ byte _rebelEmbeddedCodec45Palette[0x300];
+ byte _rebelEmbeddedCodec45Lookup[0x8000];
// Load and decode an embedded SAN animation from IACT chunk data
// userId: HUD slot (1-4), animData: raw ANIM data, size: data size, renderBitmap: current frame buffer
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index c0a7e3ab4d7..1c986483361 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -403,8 +403,9 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
frame.valid = true;
debug("Rebel2: Decoded embedded HUD (codec %d/line update): %dx%d", codec, width, height);
} else if (codec == 45) {
- // Codec 45: RA2-specific BOMP RLE (FUN_0042B5F0)
- smushDecodeRA2Bomp(frame.pixels, fobjData, 0, 0, width, height, width, dataSize);
+ // Codec 45: blur/wipe mask (FUN_0042B460 -> FUN_0042B530 -> FUN_0042DDF0)
+ smushDecodeRA2Blur(frame.pixels, fobjData, 0, 0, width, height, width, dataSize,
+ _rebelEmbeddedCodec45Palette, _rebelEmbeddedCodec45Lookup);
frame.valid = true;
} else if (codec == 23) {
// Codec 23: Skip/copy with embedded RLE (FUN_0042BBF0)
diff --git a/engines/scumm/smush/rebel/codec_ra2.cpp b/engines/scumm/smush/rebel/codec_ra2.cpp
index 44f454f88b4..e528d51f8a9 100644
--- a/engines/scumm/smush/rebel/codec_ra2.cpp
+++ b/engines/scumm/smush/rebel/codec_ra2.cpp
@@ -225,134 +225,99 @@ void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width
}
}
-/**
- * Codec 45: RA2-specific BOMP RLE with variable header
- * Used for embedded ANIM frames, particularly small animation elements.
- * Has a variable-length header (commonly 6 bytes starting with 01 FE).
- * Note: For overlay sprites, color 0 is treated as transparent.
- */
-void smushDecodeRA2Bomp(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize) {
- dst += top * pitch + left;
-
- // Detect header pattern and find RLE data start
- int headerSkip = 0;
- bool foundValidOffset = false;
-
- // Check for common 6-byte header pattern: 01 FE XX XX XX XX
- if (dataSize > 6 && src[0] == 0x01 && src[1] == 0xFE) {
- headerSkip = 6;
- foundValidOffset = true;
- } else {
- // Probe offsets to find valid RLE start
- // Valid start should have reasonable line size values
- for (int testOffset = 0; testOffset <= 6 && testOffset + 2 <= dataSize; testOffset += 2) {
- int testLineSize = READ_LE_INT16(src + testOffset);
- // A valid line size should be positive and reasonable for the width
- if (testLineSize > 0 && testLineSize <= width * 2 && testLineSize < dataSize - testOffset) {
- // Further validation: try to count valid line sizes
- int linesTest = 0;
- const byte *testPtr = src + testOffset;
- bool validSum = true;
-
- while (linesTest < height && testPtr + 2 <= src + dataSize) {
- int ls = READ_LE_INT16(testPtr);
- if (ls <= 0 || ls > width * 2) {
- validSum = false;
- break;
- }
- testPtr += ls + 2;
- linesTest++;
- }
-
- if (validSum && linesTest >= height - 1) {
- headerSkip = testOffset;
- foundValidOffset = true;
- break;
- }
- }
+bool smushPrepareRA2BlurData(const byte *src, int dataSize, byte *palette, byte *lookup, const byte *&maskData, int &maskSize) {
+ maskData = nullptr;
+ maskSize = 0;
+
+ if (src == nullptr || dataSize < 6 || palette == nullptr || lookup == nullptr)
+ return false;
+
+ // FUN_0042B530 only processes this codec body when byte +4 is 1.
+ if (src[4] != 1)
+ return false;
+
+ const int tableMode = READ_LE_INT16(src + 2);
+ maskData = src + 6;
+ maskSize = dataSize - 6;
+
+ if (tableMode == 0) {
+ if (dataSize < 0x306)
+ return false;
+
+ memcpy(palette, src + 6, 0x300);
+
+ const byte *p = src + 0x306;
+ int remaining = dataSize - 0x306;
+ int lookupIndex = 0;
+ while (lookupIndex < 0x8000 && remaining >= 2) {
+ int count = p[0];
+ const byte color = p[1];
+ p += 2;
+ remaining -= 2;
+
+ while (count-- > 0 && lookupIndex < 0x8000)
+ lookup[lookupIndex++] = color;
}
- }
- if (!foundValidOffset) {
- warning("smushDecodeRA2Bomp: Codec 45 couldn't find valid RLE offset, using offset 0");
- }
+ if (lookupIndex < 0x8000)
+ return false;
- src += headerSkip;
- const byte *dataEnd = src + (dataSize - headerSkip);
+ maskData = p;
+ maskSize = remaining;
+ }
- // Check first value to determine per-line vs continuous mode
- int firstVal = (src + 2 <= dataEnd) ? READ_LE_INT16(src) : 0;
- bool perLineMode = (firstVal > 0 && firstVal <= width * 2);
+ return maskSize > 3;
+}
- if (perLineMode) {
- // Per-line RLE with 2-byte size headers
- for (int row = 0; row < height && src < dataEnd; row++) {
- int lineSize = READ_LE_INT16(src);
- src += 2;
- if (lineSize <= 0 || lineSize > (int)(dataEnd - src))
- break;
+/**
+ * Codec 45: RA2 blur/wipe mask.
+ * Original path: FUN_0042B460 -> FUN_0042B530 -> FUN_0042DDF0.
+ */
+void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup) {
+ const byte *maskData = nullptr;
+ int maskSize = 0;
+ if (dst == nullptr || dstWidth <= 2 || dstHeight <= 2 || pitch <= 0 ||
+ !smushPrepareRA2BlurData(src, dataSize, palette, lookup, maskData, maskSize))
+ return;
- const byte *lineEnd = src + lineSize;
- byte *rowDst = dst + row * pitch;
- int x = 0;
-
- while (src < lineEnd && x < width) {
- byte ctrl = *src++;
- int count = (ctrl >> 1) + 1;
-
- if (ctrl & 1) {
- // RLE fill - color 0 is transparent for overlay sprites
- byte color = (src < lineEnd) ? *src++ : 0;
- if (color != 0) {
- int num = (count > width - x) ? width - x : count;
- memset(rowDst + x, color, num);
- }
- x += count;
- if (x > width)
- x = width;
- } else {
- // Literal copy - color 0 is transparent for overlay sprites
- for (int i = 0; i < count && x < width && src < lineEnd; i++) {
- byte color = *src++;
- if (color != 0)
- rowDst[x] = color;
- x++;
- }
- }
- }
- src = lineEnd;
- }
- } else {
- // Continuous BOMP RLE (no per-line headers)
- for (int row = 0; row < height && src < dataEnd; row++) {
- byte *rowDst = dst + row * pitch;
- int x = 0;
-
- while (x < width && src < dataEnd) {
- byte ctrl = *src++;
- int count = (ctrl >> 1) + 1;
-
- if (ctrl & 1) {
- // RLE fill - color 0 is transparent for overlay sprites
- byte color = (src < dataEnd) ? *src++ : 0;
- if (color != 0) {
- int num = (count > width - x) ? width - x : count;
- memset(rowDst + x, color, num);
- }
- x += count;
- if (x > width)
- x = width;
- } else {
- // Literal copy - color 0 is transparent for overlay sprites
- for (int i = 0; i < count && x < width && src < dataEnd; i++) {
- byte color = *src++;
- if (color != 0)
- rowDst[x] = color;
- x++;
- }
- }
+ int x = left;
+ int y = top;
+
+ while (maskSize > 3) {
+ const int dx = READ_LE_INT16(maskData);
+ const int dy = maskData[2];
+ const int count = maskData[3];
+ maskData += 4;
+ maskSize -= 4;
+
+ x += dx;
+ y += dy;
+
+ for (int i = 0; i <= count; ++i) {
+ if (x > 0 && y > 0 && x < dstWidth - 1) {
+ if (y >= dstHeight - 1)
+ return;
+
+ byte *pixel = dst + y * pitch + x;
+ const byte leftColor = pixel[-1];
+ const byte rightColor = pixel[1];
+ const byte topColor = pixel[-pitch];
+ const byte bottomColor = pixel[pitch];
+
+ const int red = palette[leftColor * 3] + palette[rightColor * 3] +
+ palette[topColor * 3] + palette[bottomColor * 3];
+ const int green = palette[leftColor * 3 + 1] + palette[rightColor * 3 + 1] +
+ palette[topColor * 3 + 1] + palette[bottomColor * 3 + 1];
+ const int blue = palette[leftColor * 3 + 2] + palette[rightColor * 3 + 2] +
+ palette[topColor * 3 + 2] + palette[bottomColor * 3 + 2];
+
+ const int lookupIndex = ((red << 5) & 0x7c00) + (green & 0x3e0) + (blue >> 5);
+ *pixel = lookup[lookupIndex & 0x7fff];
}
+
+ ++x;
}
+ --x;
}
}
diff --git a/engines/scumm/smush/rebel/codec_ra2.h b/engines/scumm/smush/rebel/codec_ra2.h
index 1aa8d2e86d0..b89797c1a0a 100644
--- a/engines/scumm/smush/rebel/codec_ra2.h
+++ b/engines/scumm/smush/rebel/codec_ra2.h
@@ -29,7 +29,7 @@ namespace Scumm {
void smushDecodeRLEOpaque(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeLineUpdate(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
void smushDecodeSkipRLE(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
-void smushDecodeRA2Bomp(byte *dst, const byte *src, int left, int top, int width, int height, int pitch, int dataSize);
+void smushDecodeRA2Blur(byte *dst, const byte *src, int left, int top, int dstWidth, int dstHeight, int pitch, int dataSize, byte *palette, byte *lookup);
const byte *smushSkipRLELines(const byte *src, int &dataSize, int lines);
} // End of namespace Scumm
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 2c43508df0d..b24ea74678c 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -140,6 +140,8 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
_ra2PendingAnimHeaderPalette = false;
+ memset(_ra2Codec45Palette, 0, sizeof(_ra2Codec45Palette));
+ memset(_ra2Codec45Lookup, 0, sizeof(_ra2Codec45Lookup));
_scrollX = 0;
_scrollY = 0;
}
@@ -726,6 +728,18 @@ void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int widt
}
bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
+ if (codec == SMUSH_CODEC_RA2_BOMP) {
+ if (_specialBuffer != nullptr) {
+ _dst = _specialBuffer;
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using _specialBuffer for codec 45 mask");
+ } else {
+ VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
+ _dst = vs->getPixels(0, 0);
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using virtual screen for codec 45 mask");
+ }
+ return true;
+ }
+
// Rebel2 allocates the low-res gameplay target as 424x260 (FUN_00424730).
// Use that target once an oversized gameplay FOBJ appears, then keep small
// overlay FOBJ chunks compositing into it. Pure 320x200 gameplay videos keep
@@ -802,8 +816,20 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
* Returns true if the codec was handled, false for standard codecs.
*/
bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
- int width, int height, int pitch, int dataSize) {
+ int width, int height, int pitch, int dataSize) {
switch (codec) {
+ case SMUSH_CODEC_RLE_ALT:
+ // RA2 codec 3 dispatches directly to FUN_0042CAA0, the opaque RLE path.
+ smushDecodeRLEOpaque(_dst, src, left, top, width, height, pitch, dataSize);
+ return true;
+ case SMUSH_CODEC_RLE:
+ if ((_curVideoFlags & 0x100) != 0) {
+ // Original RA2 RLE dispatch selects the opaque renderer only when
+ // render flag 0x100 is set. Cinematic wipe masks use transparent 0.
+ smushDecodeRLEOpaque(_dst, src, left, top, width, height, pitch, dataSize);
+ return true;
+ }
+ return false;
case SMUSH_CODEC_UNCOMPRESSED: {
const int sourcePitch = (_ra2FrameObjectOriginalWidth > 0) ? _ra2FrameObjectOriginalWidth : width;
smushDecodeRA2Uncompressed(_dst, src, left, top, width, height, pitch, sourcePitch,
@@ -822,8 +848,8 @@ bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, in
return true;
}
case SMUSH_CODEC_RA2_BOMP: {
- const uint8 *adjustedSrc = smushSkipRLELines(src, dataSize, _ra2FrameSourceSkipY);
- smushDecodeRA2Bomp(_dst, adjustedSrc, left, top, width, height, pitch, dataSize);
+ smushDecodeRA2Blur(_dst, src, left, top, pitch, _height, pitch, dataSize,
+ _ra2Codec45Palette, _ra2Codec45Lookup);
return true;
}
default:
@@ -943,10 +969,18 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
const int adjustedLeft = left + _fobjOffsetX;
_ra2FrameSourceSkipX = (adjustedLeft < 0) ? -adjustedLeft : 0;
_ra2FrameSourceSkipY = 0;
+
+ if (codec == SMUSH_CODEC_RA2_BOMP) {
+ left = adjustedLeft;
+ top += _fobjOffsetY;
+ if (srcSkipY)
+ *srcSkipY = 0;
+ return true;
+ }
+
adjustFrameCoords(left, top, width, height, pitch, &sourceSkipY);
if (codec == SMUSH_CODEC_LINE_UPDATE || codec == SMUSH_CODEC_LINE_UPDATE2 ||
- codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_RA2_BOMP ||
- codec == SMUSH_CODEC_UNCOMPRESSED) {
+ codec == SMUSH_CODEC_SKIP_RLE || codec == SMUSH_CODEC_UNCOMPRESSED) {
_ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 87c64a12431..ac4700919e8 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -89,6 +89,8 @@ private:
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
bool _ra2PendingAnimHeaderPalette;
+ byte _ra2Codec45Palette[0x300];
+ byte _ra2Codec45Lookup[0x8000];
};
} // End of namespace Scumm
Commit: 4865d1716190ff623e2da2fba5f14881a66df031
https://github.com/scummvm/scummvm/commit/4865d1716190ff623e2da2fba5f14881a66df031
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-06T20:45:51+02:00
Commit Message:
SCUMM: RA2: fixed corruption from L2 to L3
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/runlevels.cpp
engines/scumm/smush/rebel/font_rebel2.cpp
engines/scumm/smush/rebel/font_rebel2.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 8122855a018..bbde2d14589 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -2079,10 +2079,9 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
}
}
- // Load as NUT
- NutRenderer *newNut = new NutRenderer(_vm);
- newNut->loadFontFromData(nutData, bytesRead);
- if (newNut->getNumChars() <= 0) {
+ // Load as a Rebel2 embedded sprite ANIM.
+ NutRenderer *newNut = makeRebel2SpriteFromData(_vm, nutData, bytesRead);
+ if (!newNut || newNut->getNumChars() <= 0) {
debug("Rebel2 loadHandler7FlySprites: NUT load failed for par4=%d", par4);
delete newNut;
free(nutData);
@@ -2145,8 +2144,7 @@ bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 par3)
return false; // Not a turret HUD slot
}
- NutRenderer *newNut = new NutRenderer(_vm);
- newNut->loadFontFromData(animData, size);
+ NutRenderer *newNut = makeRebel2SpriteFromData(_vm, animData, size);
if (!newNut || newNut->getNumChars() <= 0) {
debug("Rebel2 loadTurretHudOverlay: NUT load failed for par3=%d", par3);
delete newNut;
@@ -2187,8 +2185,7 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
return false;
}
- NutRenderer *newNut = new NutRenderer(_vm);
- newNut->loadFontFromData(animData, size);
+ NutRenderer *newNut = makeRebel2SpriteFromData(_vm, animData, size);
if (!newNut || newNut->getNumChars() <= 0) {
debug("Rebel2 loadHandler8ShipSprites: NUT load failed for par4=%d", par4);
delete newNut;
@@ -2239,8 +2236,7 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
return false;
}
- NutRenderer *newNut = new NutRenderer(_vm);
- newNut->loadFontFromData(animData, size);
+ NutRenderer *newNut = makeRebel2SpriteFromData(_vm, animData, size);
if (!newNut || newNut->getNumChars() <= 0) {
debug("Rebel2 loadHandler25GrdSprites: NUT load failed for par4=%d", par4);
delete newNut;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index e8fe499b865..765ab1f4b22 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -434,6 +434,7 @@ public:
bool handleLevelDeath(int levelId, int phase, const char *deathVideo, const char *retryVideo, int &levelResult);
void resetLevelAttemptState(int initialPhase);
void resetLevelPhaseState(bool clearEnemies);
+ void clearEmbeddedHudFrames();
void resetLevelWaveState();
void resetHandler7FlightState();
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 55a0e046ee1..ede78f398b2 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -278,6 +278,17 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
_grd002Sprite = nullptr;
_grdShotOriginTableLoaded = false;
+ clearEmbeddedHudFrames();
+
+ free(_level2Background);
+ _level2Background = nullptr;
+ _level2BackgroundLoaded = false;
+
+ if (clearEnemies)
+ _enemies.clear();
+}
+
+void InsaneRebel2::clearEmbeddedHudFrames() {
for (uint i = 0; i < ARRAYSIZE(_rebelEmbeddedHud); ++i) {
EmbeddedSanFrame &frame = _rebelEmbeddedHud[i];
free(frame.pixels);
@@ -288,13 +299,6 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
frame.renderY = 0;
frame.valid = false;
}
-
- free(_level2Background);
- _level2Background = nullptr;
- _level2BackgroundLoaded = false;
-
- if (clearEnemies)
- _enemies.clear();
}
void InsaneRebel2::resetLevelWaveState() {
@@ -647,6 +651,8 @@ int InsaneRebel2::runLevel3() {
_playerDamage = 0;
_currentPhase = 1;
+ clearEmbeddedHudFrames();
+
// Reset bit table before gameplay starts - FUN_00423880 calls FUN_00423a00(0)
clearBit(0);
resetHandler7FlightState();
@@ -699,6 +705,8 @@ int InsaneRebel2::runLevel3() {
_playerDamage = 0;
_playerScore = phase1Score;
+ clearEmbeddedHudFrames();
+
// Reset bit table before gameplay starts
clearBit(0);
resetHandler7FlightState();
diff --git a/engines/scumm/smush/rebel/font_rebel2.cpp b/engines/scumm/smush/rebel/font_rebel2.cpp
index d83fbef6585..a9f69d0cbdb 100644
--- a/engines/scumm/smush/rebel/font_rebel2.cpp
+++ b/engines/scumm/smush/rebel/font_rebel2.cpp
@@ -27,22 +27,50 @@
#include "scumm/file.h"
#include "scumm/nut_renderer.h"
#include "scumm/scumm.h"
+#include "scumm/smush/rebel/codec_ra2.h"
namespace Scumm {
enum {
- kRebel2MaxStrings = 80
+ kRebel2MaxStrings = 80,
+ kRebel2MaxSpritePixels = 16 * 1024 * 1024,
+ kRebel2MaxDecodedSpriteBytes = 32 * 1024 * 1024
};
+struct Rebel2NutFrameInfo {
+ Rebel2NutFrameInfo() : codec(0), xoffs(0), yoffs(0), width(0), height(0), data(nullptr), dataSize(0) {}
+
+ int codec;
+ int16 xoffs;
+ int16 yoffs;
+ uint16 width;
+ uint16 height;
+ const byte *data;
+ int32 dataSize;
+};
+
+static void decodeRebel2RawSprite(byte *dst, const byte *src, int width, int height, int dataSize) {
+ const int32 totalSize = width * height;
+ const int32 copySize = MIN<int32>(totalSize, dataSize);
+ if (copySize > 0)
+ memcpy(dst, src, copySize);
+}
+
class Rebel2NutRenderer : public NutRenderer {
public:
Rebel2NutRenderer(ScummEngine *vm, const char *filename) : NutRenderer(vm, nullptr) {
loadRebel2Font(filename);
}
+ Rebel2NutRenderer(ScummEngine *vm, const byte *data, int32 dataSize) : NutRenderer(vm, nullptr) {
+ loadRebel2SpriteFromData(data, dataSize);
+ }
+
private:
void codec44(byte *dst, const byte *src, int width, int height, int pitch);
void loadRebel2Font(const char *filename);
+ void loadRebel2SpriteFromData(const byte *data, int32 dataSize);
+ void decodeRebel2Frame(byte *dst, const Rebel2NutFrameInfo &frame, byte *codec45Palette, byte *codec45Lookup);
};
void Rebel2NutRenderer::codec44(byte *dst, const byte *src, int width, int height, int pitch) {
@@ -189,10 +217,176 @@ void Rebel2NutRenderer::loadRebel2Font(const char *filename) {
delete[] dataSrc;
}
+void Rebel2NutRenderer::decodeRebel2Frame(byte *dst, const Rebel2NutFrameInfo &frame, byte *codec45Palette, byte *codec45Lookup) {
+ switch (frame.codec) {
+ case 1:
+ case 3:
+ codec1(dst, frame.data, frame.width, frame.height, frame.width);
+ break;
+ case 20:
+ decodeRebel2RawSprite(dst, frame.data, frame.width, frame.height, frame.dataSize);
+ break;
+ case 21:
+ smushDecodeLineUpdate(dst, frame.data, 0, 0, frame.width, frame.height, frame.width, frame.dataSize);
+ break;
+ case 23:
+ smushDecodeSkipRLE(dst, frame.data, 0, 0, frame.width, frame.height, frame.width, frame.dataSize);
+ break;
+ case 44:
+ codec44(dst, frame.data, frame.width, frame.height, frame.width);
+ break;
+ case 45:
+ smushDecodeRA2Blur(dst, frame.data, 0, 0, frame.width, frame.height, frame.width, frame.dataSize,
+ codec45Palette, codec45Lookup);
+ break;
+ default:
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: unknown codec: %d", frame.codec);
+ break;
+ }
+}
+
+void Rebel2NutRenderer::loadRebel2SpriteFromData(const byte *data, int32 dataSize) {
+ if (!data || dataSize < 16) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: data too small (%d bytes)", dataSize);
+ return;
+ }
+
+ if (READ_BE_UINT32(data) != MKTAG('A','N','I','M')) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: no ANIM chunk");
+ return;
+ }
+
+ uint32 length = READ_BE_UINT32(data + 4);
+ if (length > (uint32)(dataSize - 8)) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: ANIM size (%u) exceeds data size (%d)", length, dataSize);
+ length = dataSize - 8;
+ }
+
+ const int64 animEnd = 8 + (int64)length;
+ if (animEnd < 16 || READ_BE_UINT32(data + 8) != MKTAG('A','H','D','R')) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: no AHDR chunk in font data");
+ return;
+ }
+
+ int declaredChars = READ_LE_UINT16(data + 18);
+ if (declaredChars > (int)ARRAYSIZE(_chars)) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: numChars (%d) exceeds max, clamping", declaredChars);
+ declaredChars = ARRAYSIZE(_chars);
+ }
+
+ Rebel2NutFrameInfo frames[ARRAYSIZE(_chars)];
+ uint64 decodedLength = 0;
+ int frameCount = 0;
+
+ for (int64 offset = 8; offset + 8 <= animEnd && frameCount < declaredChars;) {
+ const uint32 tag = READ_BE_UINT32(data + offset);
+ const uint32 chunkSize = READ_BE_UINT32(data + offset + 4);
+ const int64 chunkDataStart = offset + 8;
+ if ((int64)chunkSize > animEnd - chunkDataStart) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: truncated chunk 0x%08x at offset %llx", tag, (long long)offset);
+ break;
+ }
+
+ const int64 chunkDataEnd = chunkDataStart + chunkSize;
+ int64 nextChunk = chunkDataEnd + (chunkSize & 1);
+ if (nextChunk > animEnd)
+ nextChunk = chunkDataEnd;
+
+ if (tag == MKTAG('F','R','M','E')) {
+ Rebel2NutFrameInfo &frame = frames[frameCount];
+ if (chunkSize >= 8 && chunkDataStart + 8 <= chunkDataEnd &&
+ READ_BE_UINT32(data + chunkDataStart) == MKTAG('F','O','B','J')) {
+ const uint32 fobjSize = READ_BE_UINT32(data + chunkDataStart + 4);
+ const int64 fobjDataStart = chunkDataStart + 8;
+ const int64 fobjDataEnd = fobjDataStart + fobjSize;
+
+ if (fobjSize >= 14 && fobjDataEnd <= chunkDataEnd) {
+ frame.codec = READ_LE_UINT16(data + fobjDataStart);
+ frame.xoffs = READ_LE_INT16(data + fobjDataStart + 2);
+ frame.yoffs = READ_LE_INT16(data + fobjDataStart + 4);
+ frame.width = READ_LE_UINT16(data + fobjDataStart + 6);
+ frame.height = READ_LE_UINT16(data + fobjDataStart + 8);
+ frame.data = data + fobjDataStart + 14;
+ frame.dataSize = fobjSize - 14;
+
+ const uint64 pixels = (uint64)frame.width * frame.height;
+ if (pixels == 0) {
+ frame.width = 0;
+ frame.height = 0;
+ frame.data = nullptr;
+ frame.dataSize = 0;
+ } else if (pixels > kRebel2MaxSpritePixels || decodedLength + pixels > kRebel2MaxDecodedSpriteBytes) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: invalid sprite dimensions %ux%u at frame %d",
+ frame.width, frame.height, frameCount);
+ frame.width = 0;
+ frame.height = 0;
+ frame.data = nullptr;
+ frame.dataSize = 0;
+ } else {
+ decodedLength += pixels;
+ }
+ }
+ }
+ frameCount++;
+ }
+
+ offset = nextChunk;
+ }
+
+ _numChars = frameCount;
+ if (_numChars <= 0) {
+ warning("Rebel2NutRenderer::loadRebel2SpriteFromData: no decodable frames");
+ return;
+ }
+
+ delete[] _decodedData;
+ _decodedData = decodedLength ? new byte[(uint32)decodedLength] : nullptr;
+ memset(_chars, 0, sizeof(_chars));
+ _fontHeight = 0;
+
+ byte *decodedPtr = _decodedData;
+ byte codec45Palette[0x300];
+ byte codec45Lookup[0x8000];
+ memset(codec45Palette, 0, sizeof(codec45Palette));
+ memset(codec45Lookup, 0, sizeof(codec45Lookup));
+
+ for (int i = 0; i < _numChars; i++) {
+ const Rebel2NutFrameInfo &frame = frames[i];
+ _chars[i].xoffs = frame.xoffs;
+ _chars[i].yoffs = frame.yoffs;
+ _chars[i].width = frame.width;
+ _chars[i].height = frame.height;
+ _chars[i].transparency = kDefaultTransparentColor;
+
+ if (frame.width == 0 || frame.height == 0 || frame.data == nullptr)
+ continue;
+
+ const uint32 pixels = frame.width * frame.height;
+ _chars[i].src = decodedPtr;
+ decodedPtr += pixels;
+ _fontHeight = MAX<int>(_fontHeight, frame.height);
+
+ memset(_chars[i].src, kDefaultTransparentColor, pixels);
+ decodeRebel2Frame(_chars[i].src, frame, codec45Palette, codec45Lookup);
+ }
+
+ debug(1, "Rebel2NutRenderer::loadRebel2SpriteFromData() - numChars=%d decodedLength=%u", _numChars, (uint32)decodedLength);
+}
+
NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename) {
return new Rebel2NutRenderer(vm, filename);
}
+NutRenderer *makeRebel2SpriteFromData(ScummEngine *vm, const byte *data, int32 dataSize) {
+ Rebel2NutRenderer *renderer = new Rebel2NutRenderer(vm, data, dataSize);
+ if (renderer->getNumChars() <= 0) {
+ delete renderer;
+ return nullptr;
+ }
+
+ return renderer;
+}
+
Rebel2FontSet::Rebel2FontSet() : numFonts(0), defaultFont(0) {
memset(fonts, 0, sizeof(fonts));
}
diff --git a/engines/scumm/smush/rebel/font_rebel2.h b/engines/scumm/smush/rebel/font_rebel2.h
index 2a5ae2f7ae7..1276e1c13a3 100644
--- a/engines/scumm/smush/rebel/font_rebel2.h
+++ b/engines/scumm/smush/rebel/font_rebel2.h
@@ -46,6 +46,7 @@ struct Rebel2FontSet {
};
NutRenderer *makeRebel2Font(ScummEngine *vm, const char *filename);
+NutRenderer *makeRebel2SpriteFromData(ScummEngine *vm, const byte *data, int32 dataSize);
int drawRebel2Char(NutRenderer *font, byte *buffer, Common::Rect &clipRect, int x, int y,
int pitch, int16 col, byte chr);
int getRebel2StringWidth(const Rebel2FontSet &fontSet, const char *str, uint len);
Commit: 084f0d6376b59d2d8aaf55b1f6390d88cbcc30a2
https://github.com/scummvm/scummvm/commit/084f0d6376b59d2d8aaf55b1f6390d88cbcc30a2
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-06T20:56:53+02:00
Commit Message:
SCUMM: RA2: reduce oscilation when using the mouse as input
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index bbde2d14589..8572dd26a67 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -36,6 +36,7 @@ namespace Scumm {
const int kRA2Handler7DirectInputNumerator = 4;
const int kRA2Handler7DirectInputDenominator = 5;
+const int kRA2Handler7MouseTargetRangeX = 0xc0;
static bool readLevel2BackgroundChunkHeader(Common::SeekableReadStream &stream, int64 containerEnd, const char *context,
uint32 &tag, uint32 &chunkSize, int64 &dataEnd, int64 &nextChunkPos) {
@@ -860,6 +861,12 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
kRA2Handler7DirectInputDenominator);
scaledInputY = (int16)((scaledInputY * kRA2Handler7DirectInputNumerator) /
kRA2Handler7DirectInputDenominator);
+ const bool useMouseFlightTarget = !_gamepadAimActive;
+ int16 mouseFlightTargetX = _flyShipScreenX;
+ if (useMouseFlightTarget) {
+ mouseFlightTargetX = (int16)(0xd4 + (scaledInputX * kRA2Handler7MouseTargetRangeX) / 127);
+ mouseFlightTargetX = CLIP<int16>(mouseFlightTargetX, 0x14, 0x194);
+ }
// Step 3: Velocity history + smoothed average (lines 141-157).
for (int i = 24; i > 0; i--) {
@@ -920,6 +927,16 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
if (positionDeltaX > 11)
positionDeltaX = 12;
+ // Retail integrates relative flight axes. ScummVM's real mouse is an
+ // absolute position, so steer toward a bounded position target instead of
+ // letting a held off-center cursor keep pushing the ship until it bounces.
+ if (useMouseFlightTarget) {
+ int targetDeltaX = mouseFlightTargetX - _flyShipScreenX;
+ positionDeltaX = (int16)CLIP<int>(targetDeltaX / 4, -12, 12);
+ if (positionDeltaX == 0 && targetDeltaX != 0)
+ positionDeltaX = (targetDeltaX < 0) ? -1 : 1;
+ }
+
// Apply X delta (line 193 / 237).
_flyShipScreenX += positionDeltaX;
@@ -1076,11 +1093,12 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
((_vm->VAR(_vm->VAR_LEFTBTN_HOLD) != 0) ||
_vm->getActionState(kScummActionInsaneAttack));
- debugC(DEBUG_INSANE, "Rebel2 H7: mouse=(%d,%d) raw=(%d,%d) scaled=(%d,%d) pos=(%d,%d) "
- "vel=%d vIn=%d dx=%d dir=%d mode=%d",
+ debugC(DEBUG_INSANE, "Rebel2 H7: mouse=(%d,%d) raw=(%d,%d) scaled=(%d,%d) targetX=%d pos=(%d,%d) "
+ "vel=%d vIn=%d dx=%d dir=%d mode=%d mouseTarget=%d",
mouseX, mouseY, inputX, inputY, scaledInputX, scaledInputY,
- _flyShipScreenX, _flyShipScreenY, _smoothedVelocity,
- _verticalInput, positionDeltaX, _shipDirectionIndex, _flyControlMode);
+ mouseFlightTargetX, _flyShipScreenX, _flyShipScreenY, _smoothedVelocity,
+ _verticalInput, positionDeltaX, _shipDirectionIndex, _flyControlMode,
+ useMouseFlightTarget ? 1 : 0);
}
// ScummVM refactor helper for opcode 6 Handler 25, not a separate retail function.
More information about the Scummvm-git-logs
mailing list