[Scummvm-git-logs] scummvm master -> e0985e843eeb5e4e9cb075300c5f71036fc754f8
neuromancer
noreply at scummvm.org
Wed Jun 3 17:38:05 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:
4de0e53e91 SCUMM: RA2: improve rendering of level 12
d2be31c84a SCUMM: RA2: make level 12 progress until the end
5905f3934c SCUMM: RA2: proper shooting in level 12
3f6b1ed8e4 SCUMM: RA2: render overlay in level 12
e0985e843e SCUMM: RA2: removed some useless statics
Commit: 4de0e53e915325440cccd4fe3179ab313c3b3e78
https://github.com/scummvm/scummvm/commit/4de0e53e915325440cccd4fe3179ab313c3b3e78
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T16:59:37+02:00
Commit Message:
SCUMM: RA2: improve rendering of level 12
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index c7d1bf5ca9f..906160a537e 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -86,7 +86,7 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
// This is called at the start of handleFrame(), before any FOBJ chunks are processed.
//
// IMPORTANT: Only restore when the render buffer pitch matches the background pitch (320).
- // Levels like Level 12 (Sewers) use oversized buffers (640x260) where FOBJ/FETCH handles
+ // Levels like Level 12 (Sewers) use oversized buffers (424x260) where FOBJ/FETCH handles
// background restoration. Copying the 320-wide background into a 640-wide buffer with
// hardcoded pitch=320 would corrupt the corridor rendering.
if (_rebelHandler == 8 && _level2BackgroundLoaded && _level2Background && renderBitmap) {
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index db4780badeb..afabee2e76a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -3340,7 +3340,7 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
return;
// CRITICAL: Clip height to 180 (0xb4) + viewport Y to avoid drawing over status bar.
- // For oversized buffers (e.g., Level 12's 640x260), the status bar is at
+ // For oversized buffers (e.g., Level 12's 424x260), the status bar is at
// Y = 180 + _viewY in buffer coordinates.
int renderHeight = MIN(height, 180 + _viewY);
@@ -3378,7 +3378,7 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
// Add viewport offset so sprite follows the visible area.
// For 320x200 buffers (Level 2), _viewX/_viewY are 0 â no change.
- // For oversized buffers (Level 12's 640x260), the viewport scrolls
+ // For oversized buffers (Level 12's 424x260), the viewport scrolls
// and sprites must be drawn at the correct position within it.
int drawX = _rebelViewOffset2X + spriteXOffset + _viewX;
int drawY = _rebelViewOffset2Y + spriteYOffset + _viewY;
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 64f483df637..7389e22422c 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -41,6 +41,10 @@
namespace Scumm {
+bool isRebel2FullFrameDeltaCodec(int codec) {
+ return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
+}
+
// ---------------------------------------------------------------------------
// SmushPlayerRebel2 â construction / destruction
// ---------------------------------------------------------------------------
@@ -687,17 +691,16 @@ void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int widt
_ra2FrameObjectSurfaceHeight = (int)bottom;
}
-bool SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
+bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
// Rebel2 uses a special buffer for all non-matching frames.
- // Oversized FOBJ chunks are clipped against the original 424x260 surface.
- // Level 1: First frame is 424x260 (background), small sprites reuse same buffer
- // Level 2: Uses virtual screen directly (handled below when _specialBuffer stays null)
+ // Oversized full-frame delta codecs decode into a tightly packed surface
+ // with their own width as pitch. Other codecs honor left/top and pitch.
const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
const int64 fobjSize64 = (int64)width * height;
int surfaceWidth = width;
int surfaceHeight = height;
- if (fobjSize64 > screenSize) {
+ if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
surfaceWidth = MAX(surfaceWidth, _ra2FrameObjectSurfaceWidth);
surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
}
@@ -721,13 +724,13 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int width, int height) {
free(_specialBuffer);
_specialBuffer = newSpecialBuffer;
_specialBufferSize = bufSize;
- _width = surfaceWidth;
- _height = surfaceHeight;
// Zero-fill the new buffer to avoid garbage in areas not written by FOBJ codec
memset(_specialBuffer, 0, bufSize);
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Allocated new _specialBuffer %dx%d for FOBJ %dx%d (%d bytes)",
surfaceWidth, surfaceHeight, width, height, bufSize);
}
+ _width = surfaceWidth;
+ _height = surfaceHeight;
}
if (bufSize > screenSize &&
@@ -859,7 +862,7 @@ void SmushPlayerRebel2::handleGameParseNextFrame() {
bool SmushPlayerRebel2::handleGameFrameBufferSelect(int codec, int width, int height) {
if ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)) {
- return ra2SelectFrameBuffer(width, height);
+ return ra2SelectFrameBuffer(codec, width, height);
}
return false;
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 5f4a33dbae8..20c20c55a29 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -68,7 +68,7 @@ private:
int pos_x, int pos_y, int left, int top,
int width, int height, TextStyleFlags flg);
void ra2PrepareFrameObjectSurface(int left, int top, int width, int height);
- bool ra2SelectFrameBuffer(int width, int height);
+ bool ra2SelectFrameBuffer(int codec, int width, int height);
bool ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize);
void ra2HandleDeltaPalette(int32 subSize, Common::SeekableReadStream &b);
Commit: d2be31c84aeb11e949ed8ce7c71e4ec06bb78d6d
https://github.com/scummvm/scummvm/commit/d2be31c84aeb11e949ed8ce7c71e4ec06bb78d6d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T17:39:38+02:00
Commit Message:
SCUMM: RA2: make level 12 progress until the end
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 906160a537e..15e1a040343 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -602,22 +602,30 @@ void InsaneRebel2::updateOpcode6Handler(int16 par2) {
}
// ScummVM refactor helper for opcode 6 Handler 8, not a separate retail function.
-void InsaneRebel2::handleOpcode6Handler8(int16 par3, int16 par4) {
+void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 par4) {
// Handler 8 specific logic (third-person on foot) - FUN_00401234 case 4.
- // Set ship level mode (DAT_0043e000 = par3)
- _shipLevelMode = par3;
+ // DAT_0043e000 = local_14[3], which maps to the IACT header's par4/userId.
+ _shipLevelMode = par4;
- // If par4 == 1, enable status bar and re-render laser texture (FUN_0040bb87)
- if (par4 == 1) {
+ // local_14[4] is the first body word after the 8-byte IACT header.
+ int16 bodyStatusFlag = 0;
+ if (b.pos() + 2 <= b.size()) {
+ int64 savedPos = b.pos();
+ bodyStatusFlag = b.readSint16LE();
+ b.seek(savedPos);
+ }
+
+ // If local_14[4] == 1, enable status bar and re-render laser texture (FUN_0040bb87)
+ if (bodyStatusFlag == 1) {
_rebelStatusBarSprite = 5;
if (_smush_iconsNut && _smush_iconsNut->getNumChars() > 5) {
initLaserTexture(_smush_iconsNut, 5);
}
}
- // Reset state when shipLevelMode != 0 && par4 == 1 (FUN_401234 lines 97-103)
+ // Reset state when shipLevelMode != 0 && local_14[4] == 1 (FUN_00401234 lines 97-103)
// Guard with _rebelOp6Initialized: runs once per wave video, not per frame.
- if (_shipLevelMode != 0 && par4 == 1 && !_rebelOp6Initialized) {
+ if (_shipLevelMode != 0 && bodyStatusFlag == 1 && !_rebelOp6Initialized) {
clearBit(0);
for (int i = 0; i < 512; i++) {
_rebelLinks[i][0] = 0;
@@ -758,8 +766,8 @@ void InsaneRebel2::handleOpcode6Handler8(int16 par3, int16 par4) {
_vm->getActionState(kScummActionInsaneAttack);
}
- debug("Rebel2 Opcode 6 (Handler 8): mode=%d range=%d shipPos=(%d,%d) target=(%d,%d) firing=%d dir=(%d,%d,%d)",
- _shipLevelMode, _movementRangeLimit, _shipPosX, _shipPosY, _shipTargetX, _shipTargetY, _shipFiring,
+ debug("Rebel2 Opcode 6 (Handler 8): mode=%d bodyFlag=%d range=%d shipPos=(%d,%d) target=(%d,%d) firing=%d dir=(%d,%d,%d)",
+ _shipLevelMode, bodyStatusFlag, _movementRangeLimit, _shipPosX, _shipPosY, _shipTargetX, _shipTargetY, _shipFiring,
_shipDirectionH, _shipDirectionV, _shipDirectionIndex);
}
@@ -1465,20 +1473,20 @@ void InsaneRebel2::scanOpcode6EmbeddedAnim(byte *renderBitmap, Common::SeekableR
// iactRebel2Opcode6 -- Level setup / mode switch (FUN_41CADB case 4)
//
// Per-wave initialization: clears bit table, resets link tables, configures
-// handler mode (ship/turret/corridor), and loads collision zones. Called once
-// per wave video on frame 0.
+// handler mode (ship/turret/corridor), and loads collision zones. The original
+// gates the reset with handler-specific IACT fields, not always frame 0.
//
void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4) {
// Opcode 6: Level setup / mode switch
// Based on FUN_41CADB case 4 (switch on *local_14 - 2 == 4, meaning opcode 6)
//
// For Handler 8 (third-person on foot) - FUN_00401234 case 4:
- // - par3 sets ship level mode (DAT_0043e000)
- // - par4 == 1 triggers status bar display and state reset
+ // - par4 sets ship level mode (DAT_0043e000)
+ // - first body word == 1 triggers status bar display and state reset
// - Updates ship position based on mouse input
//
// For Handler 0x26/0x19 (turret/FPS):
- // - Same par4 == 1 behavior
+ // - Handler-specific status/reset word
// - Different view offset calculations
debug("Rebel2 IACT Opcode 6: par2=%d par3=%d par4=%d", par2, par3, par4);
@@ -1486,7 +1494,7 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
updateOpcode6Handler(par2);
if (_rebelHandler == 8) {
- handleOpcode6Handler8(par3, par4);
+ handleOpcode6Handler8(b, par4);
return;
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index f2d5cdb3f78..8dc6ceb75d0 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -538,7 +538,7 @@ public:
void iactRebel2Opcode3(Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
void iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStream &b, int32 chunkSize, int16 par2, int16 par3, int16 par4);
void updateOpcode6Handler(int16 par2);
- void handleOpcode6Handler8(int16 par3, int16 par4);
+ void handleOpcode6Handler8(Common::SeekableReadStream &b, int16 par4);
void handleOpcode6Handler7(Common::SeekableReadStream &b, int16 par4);
void handleOpcode6Handler25(byte *renderBitmap, Common::SeekableReadStream &b, int16 par2, int16 par3, int16 par4);
void handleOpcode6Turret(Common::SeekableReadStream &b, int16 par4);
@@ -1063,7 +1063,7 @@ public:
int16 _shipTargetY; // DAT_0043e004 - Target Y
// Level mode for handler 8 (different from _rebelLevelType)
- // Set by opcode 6 par3, affects ship rendering behavior
+ // Set by opcode 6 par4, affects ship rendering behavior
// Mode 0/1/3: "Shooting" - full movement range (127)
// Mode 2: "Covered" - restricted movement (41) - behind cover
// Mode 4: "Autopilot" - no shooting, scripted movement
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 7389e22422c..3b0d660c7fb 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -41,6 +41,9 @@
namespace Scumm {
+// FUN_00423880 is initialized at startup with a 400000-byte preload buffer.
+constexpr int kRebel2LoadBufferSize = 400000;
+
bool isRebel2FullFrameDeltaCodec(int codec) {
return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
}
@@ -89,7 +92,7 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_loadBufferOffset = 0;
_loadReadOffset = 8; // Original starts reading at offset 8 (skips header)
_lastLoadChunkIdx = -1;
- _totalLoadChunks = 0;
+ _loadStreamId = 0;
_ra2FrameSourceSkipY = 0;
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
@@ -141,19 +144,16 @@ void SmushPlayerRebel2::initGameVideoState() {
/**
* RA2-specific cleanup in SmushPlayer::release().
- * Frees stored FOBJ data but preserves _frameBuffer across videos.
+ * Preserves the stored FOBJ and _frameBuffer across videos.
*/
void SmushPlayerRebel2::releaseGameVideoState() {
- free(_storedFobjData);
- _storedFobjData = nullptr;
- _storedFobjDataSize = 0;
- _storedFobjParm2 = 0;
free(_lastFobjData);
_lastFobjData = nullptr;
_lastFobjDataSize = 0;
_hasFrameFobjForGost = false;
- // Preserve _frameBuffer across videos so that gameplay videos (which have no
- // background FOBJ) can use the stored background from the previous BEG video.
+ // FUN_00423880 allocates the STOR overlay buffer once for the SMUSH subsystem,
+ // and FUN_004246d0 replays the last stored FOBJ even after a new SAN starts.
+ // Level 12 wave segments depend on this when 12P01_B.SAN starts with FTCH.
}
/**
@@ -585,7 +585,7 @@ void SmushPlayerRebel2::handleGameLoad(int32 subSize, Common::SeekableReadStream
* Handle LOAD chunk for Rebel Assault 2.
*
* LOAD chunks stream embedded resource data across multiple frames.
- * The data is accumulated in a buffer and consumed by the audio system.
+ * FUN_00424450 treats word 0 as the stream id and word 1 as the chunk index.
*/
void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad()");
@@ -595,26 +595,24 @@ void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b)
return;
}
- int16 totalChunks = b.readUint16LE();
+ int16 streamId = b.readUint16LE();
int16 chunkIndex = b.readUint16LE();
b.skip(6); // Unknown/padding
int32 dataSize = subSize - 10;
- debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: chunk %d/%d, dataSize=%d, bufferOffset=%d",
- chunkIndex, totalChunks, dataSize, _loadBufferOffset);
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: stream=%d chunk=%d dataSize=%d bufferOffset=%d",
+ streamId, chunkIndex, dataSize, _loadBufferOffset);
- // First chunk in sequence - reset buffer state
if (chunkIndex == 0) {
_loadBufferOffset = 0;
_loadReadOffset = 8;
_lastLoadChunkIdx = -1;
- _totalLoadChunks = totalChunks;
+ _loadStreamId = streamId;
- int32 estimatedSize = totalChunks * 600;
- if (_loadBuffer == nullptr || _loadBufferSize < estimatedSize) {
+ if (_loadBuffer == nullptr || _loadBufferSize < kRebel2LoadBufferSize) {
free(_loadBuffer);
- _loadBufferSize = estimatedSize;
+ _loadBufferSize = kRebel2LoadBufferSize;
_loadBuffer = (byte *)malloc(_loadBufferSize);
if (_loadBuffer == nullptr) {
warning("SmushPlayerRebel2::handleLoad: Failed to allocate %d bytes for LOAD buffer",
@@ -627,31 +625,23 @@ void SmushPlayerRebel2::handleLoad(int32 subSize, Common::SeekableReadStream &b)
}
}
- // Check sequential order
if (_lastLoadChunkIdx + 1 != chunkIndex) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Non-sequential chunk %d (expected %d), skipping",
chunkIndex, _lastLoadChunkIdx + 1);
return;
}
- // Check buffer capacity
- if (_loadBuffer == nullptr || _loadBufferOffset + dataSize >= _loadBufferSize) {
+ if (_loadBuffer == nullptr || _loadBufferOffset + subSize >= _loadBufferSize) {
warning("SmushPlayerRebel2::handleLoad: Buffer overflow - offset=%d size=%d limit=%d",
- _loadBufferOffset, dataSize, _loadBufferSize);
+ _loadBufferOffset, subSize, _loadBufferSize);
return;
}
- // Copy data to buffer
b.read(_loadBuffer + _loadBufferOffset, dataSize);
_loadBufferOffset += dataSize;
_lastLoadChunkIdx = chunkIndex;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Accumulated %d bytes total", _loadBufferOffset);
-
- if (chunkIndex == totalChunks - 1) {
- debugC(DEBUG_SMUSH, "SmushPlayerRebel2::handleLoad: Sequence complete - %d chunks, %d bytes total",
- totalChunks, _loadBufferOffset);
- }
}
/**
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 20c20c55a29..35af253e0f6 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -82,7 +82,7 @@ private:
int32 _loadBufferOffset;
int32 _loadReadOffset;
int16 _lastLoadChunkIdx;
- int16 _totalLoadChunks;
+ int16 _loadStreamId;
int _ra2FrameSourceSkipY;
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
Commit: 5905f3934ccee38d0a2d1348619e51c55deefece
https://github.com/scummvm/scummvm/commit/5905f3934ccee38d0a2d1348619e51c55deefece
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T17:58:24+02:00
Commit Message:
SCUMM: RA2: proper shooting in level 12
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 15e1a040343..60c194e6a0d 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -2151,8 +2151,8 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
// par4 values (from IACT data offset +6, NOT par3 which is always 0):
// 1: POV001 - Primary ship sprite (DAT_0047e010 / _shipSprite)
// 3: POV004 - Secondary ship sprite (DAT_0047e028 / _shipSprite2)
- // 6: POV002 - Ship overlay 1 (DAT_0047e020 / _shipOverlay1)
- // 7: POV003 - Ship overlay 2 (DAT_0047e018 / _shipOverlay2)
+ // 6: POV002 - Shot impact overlay (DAT_0047e020 / _shipOverlay1)
+ // 7: POV003 - Shot impact overlay (DAT_0047e018 / _shipOverlay2)
if (!animData || size <= 0) {
return false;
@@ -2183,11 +2183,11 @@ bool InsaneRebel2::loadHandler8ShipSprites(byte *animData, int32 size, int16 par
delete _shipSprite2;
_shipSprite2 = newNut;
break;
- case 6: // POV002 - Ship overlay 1
+ case 6: // POV002 - Shot impact overlay
delete _shipOverlay1;
_shipOverlay1 = newNut;
break;
- case 7: // POV003 - Ship overlay 2
+ case 7: // POV003 - Shot impact overlay
delete _shipOverlay2;
_shipOverlay2 = newNut;
break;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index a777dc8353e..a3acf396b74 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -347,6 +347,13 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_vehicleShots[i].targetX = 0;
_vehicleShots[i].targetY = 0;
}
+ for (i = 0; i < 7; i++) {
+ _vehicleShotImpacts[i].counter = 0;
+ _vehicleShotImpacts[i].x = 0;
+ _vehicleShotImpacts[i].y = 0;
+ _vehicleShotImpacts[i].spriteIndex = 0;
+ }
+ _vehicleShotImpactIndex = 0;
// Initialize Handler 7 Space shot system (FUN_40FADF)
for (i = 0; i < 2; i++) {
@@ -1501,10 +1508,25 @@ int32 InsaneRebel2::processMouse() {
// - Handler 25 keeps edge-triggered clicks due cover-toggle/sticky input semantics.
// - Other gameplay handlers fire while button is held; slot counters still rate-limit.
bool triggerShot = (_rebelHandler == 25) ? (leftPressed && !leftWasPressed) : leftPressed;
- if (triggerShot && isShootingAllowed()) {
- Common::Point mousePos = (_rebelHandler == 7) ? getHandler7ShotTargetPoint() : getGameplayAimPoint();
- debug("Rebel2 Click: Mouse=(%d,%d) Enemies=%d",
- mousePos.x, mousePos.y, _enemies.size());
+ bool canShoot = isShootingAllowed();
+ if (_rebelHandler == 8) {
+ // FUN_00401CCF uses the same per-frame fire bit both to spawn shots and
+ // to choose the POV gun sprite. Keep the sprite driven by the event-manager
+ // input that processMouse() uses during SMUSH playback.
+ _shipFiring = triggerShot && canShoot;
+ }
+ if (triggerShot && canShoot) {
+ Common::Point mousePos;
+ if (_rebelHandler == 7) {
+ mousePos = getHandler7ShotTargetPoint();
+ } else if (_rebelHandler == 8) {
+ mousePos = getHandler8ShotTargetPoint();
+ } else {
+ mousePos = getGameplayAimPoint();
+ }
+ Common::Point gameplayAim = getGameplayAimPoint();
+ debug("Rebel2 Click: Mouse=(%d,%d) Target=(%d,%d) Enemies=%d",
+ gameplayAim.x, gameplayAim.y, mousePos.x, mousePos.y, _enemies.size());
// Spawn visual shot immediately
spawnShot(mousePos.x, mousePos.y);
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 8dc6ceb75d0..54489204056 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -596,6 +596,7 @@ public:
// Draw Handler 8 ship sprite (third-person on foot - POV sprites)
void renderHandler8Ship(byte *renderBitmap, int pitch, int width, int height);
+ void renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height); // FUN_402DA8 (Handler 8)
// Draw fallback ship using embedded HUD frame
void renderFallbackShip(byte *renderBitmap, int pitch, int width, int height);
@@ -614,6 +615,7 @@ public:
Common::Point getHandler7ShipDrawPoint();
Common::Point getHandler7ProjectedPoint();
Common::Point getHandler7ShotTargetPoint();
+ Common::Point getHandler8ShotTargetPoint();
// Reset enemy active flags and collision zones at frame end
void frameEndCleanup();
@@ -969,6 +971,21 @@ public:
};
VehicleShot _vehicleShots[2];
+ // Handler 8 shot impact animation state (FUN_402DA8 / FUN_402ED0)
+ // DAT_0043f81a[7]: impact duration counter
+ // DAT_0043f828[7]: impact world X
+ // DAT_0043f836[7]: impact world Y
+ // DAT_0043f844[7]: impact sprite index from DAT_0047e030 background mask
+ // DAT_0043f852: ring-buffer index
+ struct VehicleShotImpact {
+ int16 counter;
+ int16 x;
+ int16 y;
+ int16 spriteIndex;
+ };
+ VehicleShotImpact _vehicleShotImpacts[7];
+ int16 _vehicleShotImpactIndex;
+
// ---------------------------------------------------------------------------
// Handler 7 Third-Person Ship Shot System
// ---------------------------------------------------------------------------
@@ -1035,16 +1052,16 @@ public:
// Based on FUN_00401234 and FUN_00401ccf disassembly:
// - DAT_0047e010: Primary ship sprite (POV001, subcase 1)
// - DAT_0047e028: Secondary ship sprite (POV004, subcase 3)
- // - DAT_0047e020: Additional overlay (POV002, subcase 6)
- // - DAT_0047e018: Additional overlay (POV003, subcase 7)
+ // - DAT_0047e020: Shot impact overlay (POV002, subcase 6)
+ // - DAT_0047e018: Shot impact overlay (POV003, subcase 7)
// - DAT_0043e006: Ship X position (raw, needs conversion for display)
// - DAT_0043e008: Ship Y position (raw, needs conversion for display)
- // - DAT_0043e000: Level mode from opcode 6 par3
+ // - DAT_0043e000: Level mode from opcode 6 par4
NutRenderer *_shipSprite; // DAT_0047e010 - Primary ship NUT
NutRenderer *_shipSprite2; // DAT_0047e028 - Secondary ship NUT
- NutRenderer *_shipOverlay1; // DAT_0047e020 - Additional overlay
- NutRenderer *_shipOverlay2; // DAT_0047e018 - Additional overlay
+ NutRenderer *_shipOverlay1; // DAT_0047e020 - Shot impact overlay
+ NutRenderer *_shipOverlay2; // DAT_0047e018 - Shot impact overlay
// Level 2 background buffer (DAT_0047e030)
// Loaded from IACT opcode 8, par4=5 - contains 320x200 background image
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index afabee2e76a..97d32232cc0 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -589,6 +589,13 @@ Common::Point InsaneRebel2::getHandler7ShotTargetPoint() {
projected.y + ABS(_smoothedVelocity) / 4 - _verticalInput / 2 - 0x28);
}
+Common::Point InsaneRebel2::getHandler8ShotTargetPoint() {
+ // Retail handler 8 stores and draws the shot target from the damped ship
+ // position in FUN_00401ccf, not from the current mouse/analog aim point.
+ return Common::Point(((_shipPosX - 0xa0) >> 3) + 0xa0,
+ ((_shipPosY - 0x28) >> 2) + 0x69);
+}
+
// spawnSpaceShot -- Handler 7 space combat shot spawn (FUN_40D836).
void InsaneRebel2::spawnSpaceShot(int x, int y) {
for (int i = 0; i < 2; i++) {
@@ -2484,6 +2491,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
_flyShipSprite ? _flyShipSprite->getNumChars() : 0);
renderHandler7Ship(renderBitmap, pitch, width, height);
+ renderVehicleShotImpacts(renderBitmap, pitch, width, height);
renderHandler8Ship(renderBitmap, pitch, width, height);
renderFallbackShip(renderBitmap, pitch, width, height);
@@ -3295,29 +3303,6 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
}
}
- // Also render ship overlays (POV002 / POV003) if loaded.
- // These are weapon/character overlays loaded via IACT opcode 8 par4=6,7.
- if (_shipOverlay1) {
- int overlayIdx = _shipFiring ? 1 : 0;
- if (overlayIdx >= _shipOverlay1->getNumChars())
- overlayIdx = 0;
- int16 ovlXOff = _shipOverlay1->getCharXOffset(overlayIdx);
- int16 ovlYOff = _shipOverlay1->getCharYOffset(overlayIdx);
- int ovlX = displayOffsetX + ovlXOff + _viewX;
- int ovlY = displayOffsetY + ovlYOff + _viewY;
- renderNutSprite(renderBitmap, pitch, width, height, ovlX, ovlY, _shipOverlay1, overlayIdx);
- }
- if (_shipOverlay2) {
- int overlayIdx = _shipFiring ? 1 : 0;
- if (overlayIdx >= _shipOverlay2->getNumChars())
- overlayIdx = 0;
- int16 ovlXOff = _shipOverlay2->getCharXOffset(overlayIdx);
- int16 ovlYOff = _shipOverlay2->getCharYOffset(overlayIdx);
- int ovlX = displayOffsetX + ovlXOff + _viewX;
- int ovlY = displayOffsetY + ovlYOff + _viewY;
- renderNutSprite(renderBitmap, pitch, width, height, ovlX, ovlY, _shipOverlay2, overlayIdx);
- }
-
int sprW = _shipSprite->getCharWidth(spriteIndex);
int sprH = _shipSprite->getCharHeight(spriteIndex);
debug("Rebel2 Handler8: Ship at (%d,%d) raw(%d,%d) offset(%d,%d) nutOff(%d,%d) size(%d,%d) bottom=%d view(%d,%d) sprite=%d/%d",
@@ -3326,6 +3311,41 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
_viewX, _viewY, spriteIndex, numSprites);
}
+// renderVehicleShotImpacts -- Handler 8 shot impact sprites (FUN_402DA8).
+void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int width, int height) {
+ if (_rebelHandler != 8)
+ return;
+
+ for (int i = 0; i < 7; i++) {
+ VehicleShotImpact &impact = _vehicleShotImpacts[i];
+ if (impact.counter <= 0)
+ continue;
+
+ int drawX = impact.x - _shipPosX + _viewX;
+ int drawY = impact.y - _shipPosY + _viewY;
+
+ // Original draws DAT_0047e020 repeatedly based on remaining life, then
+ // DAT_0047e018 once, both using the sampled background-mask sprite index.
+ if (_shipOverlay1 && impact.spriteIndex >= 0 && impact.spriteIndex < _shipOverlay1->getNumChars()) {
+ int spriteX = drawX + _shipOverlay1->getCharXOffset(impact.spriteIndex) -
+ _shipOverlay1->getCharWidth(impact.spriteIndex) / 2;
+ int spriteY = drawY + _shipOverlay1->getCharYOffset(impact.spriteIndex) -
+ _shipOverlay1->getCharHeight(impact.spriteIndex) / 2;
+ for (int repeat = 0; repeat <= (impact.counter >> 2); repeat++)
+ renderNutSprite(renderBitmap, pitch, width, height, spriteX, spriteY, _shipOverlay1, impact.spriteIndex);
+ }
+ if (_shipOverlay2 && impact.spriteIndex >= 0 && impact.spriteIndex < _shipOverlay2->getNumChars()) {
+ int spriteX = drawX + _shipOverlay2->getCharXOffset(impact.spriteIndex) -
+ _shipOverlay2->getCharWidth(impact.spriteIndex) / 2;
+ int spriteY = drawY + _shipOverlay2->getCharYOffset(impact.spriteIndex) -
+ _shipOverlay2->getCharHeight(impact.spriteIndex) / 2;
+ renderNutSprite(renderBitmap, pitch, width, height, spriteX, spriteY, _shipOverlay2, impact.spriteIndex);
+ }
+
+ impact.counter--;
+ }
+}
+
// Handler 25: Draw GRD001 (wall/cockpit overlay) in procPostRendering.
// renderHandler25ShipPre -- Handler 25 GRD001 pre-rendering (FUN_0041DB5E lines 202-221).
// GRD sprites drawn AFTER FOBJ enemies, before GRD002. Mode-based clipping:
@@ -4045,6 +4065,32 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
progress, maxDuration, 20, 8, 4);
_vehicleShots[i].counter--;
+
+ // FUN_402ED0 samples DAT_0047e030 after drawing the beam. Non-zero
+ // mask pixels select the POV002/POV003 impact sprite index rendered by
+ // FUN_402DA8 on following frames. Mode 2 suppresses these impacts.
+ if (_shipLevelMode != 2 && _level2BackgroundLoaded && _level2Background) {
+ int impactX = ((_shipPosX - 160) >> 3) + _shipPosX + 160;
+ int impactY = ((_shipPosY - 40) >> 2) + _shipPosY + 105;
+ int maskX = impactX - 160;
+ int maskY = impactY - 30;
+
+ if (impactX > 160 && impactX < 480 &&
+ maskX >= 0 && maskX < 320 && maskY >= 0 && maskY < 200) {
+ byte spriteIndex = _level2Background[maskY * 320 + maskX];
+ if (spriteIndex != 0) {
+ VehicleShotImpact &impact = _vehicleShotImpacts[_vehicleShotImpactIndex];
+ _vehicleShotImpactIndex++;
+ if (_vehicleShotImpactIndex >= 7)
+ _vehicleShotImpactIndex = 0;
+
+ impact.counter = 12;
+ impact.x = impactX;
+ impact.y = impactY;
+ impact.spriteIndex = spriteIndex;
+ }
+ }
+ }
}
}
@@ -4130,7 +4176,7 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
}
}
-// renderCrosshair -- Draw crosshair/reticle at mouse position.
+// renderCrosshair -- Draw crosshair/reticle at the current handler's aim point.
void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int height) {
// From FUN_0040d836 (Handler 7) line 167-168: crosshair only drawn when DAT_004437c0 == 2
// Don't draw crosshair when shooting is disabled (flight-only segments)
@@ -4147,7 +4193,14 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
// Update target lock state and draw crosshair/reticle
// Target lock detection (DAT_00443676 equivalent)
- Common::Point aimPos = (_rebelHandler == 7) ? getHandler7ShotTargetPoint() : getGameplayAimPoint();
+ Common::Point aimPos;
+ if (_rebelHandler == 7) {
+ aimPos = getHandler7ShotTargetPoint();
+ } else if (_rebelHandler == 8) {
+ aimPos = getHandler8ShotTargetPoint();
+ } else {
+ aimPos = getGameplayAimPoint();
+ }
Common::Point worldMousePos = aimPos;
if (_rebelHandler != 7) {
worldMousePos.x += _viewX;
@@ -4199,8 +4252,8 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
int ch = _smush_iconsNut->getCharHeight(reticleIndex);
// Calculate crosshair position
- int crosshairX = aimPos.x - cw / 2;
- int crosshairY = aimPos.y - ch / 2;
+ int crosshairX = aimPos.x;
+ int crosshairY = aimPos.y;
if (_rebelHandler != 7) {
crosshairX += _viewX;
crosshairY += _viewY;
@@ -4213,6 +4266,10 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
crosshairY += _rebelViewOffsetY;
}
+ // FUN_004236e0 with flags=2 applies the NUT frame offsets, then centers.
+ crosshairX += _smush_iconsNut->getCharXOffset(reticleIndex) - cw / 2;
+ crosshairY += _smush_iconsNut->getCharYOffset(reticleIndex) - ch / 2;
+
renderNutSprite(renderBitmap, pitch, width, height,
crosshairX, crosshairY,
_smush_iconsNut, reticleIndex);
Commit: 3f6b1ed8e45ffcd86415bf13754c70eb33749f7b
https://github.com/scummvm/scummvm/commit/3f6b1ed8e45ffcd86415bf13754c70eb33749f7b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T19:34:28+02:00
Commit Message:
SCUMM: RA2: render overlay in level 12
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 60c194e6a0d..854fcf100df 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -713,6 +713,15 @@ void InsaneRebel2::handleOpcode6Handler8(Common::SeekableReadStream &b, int16 pa
_shipPosY = (newY < _shipTargetY) ? _shipTargetY : newY;
}
+ // FUN_00401234 calls FUN_00424510(-DAT_0043e006, -DAT_0043e008)
+ // after updating the handler-8 camera. This shifts subsequent FOBJ
+ // decoding into screen coordinates; FUN_00401CCF then draws HUD and
+ // weapon sprites without a separate final-buffer scroll.
+ if (_player) {
+ _player->_fobjOffsetX = -_shipPosX;
+ _player->_fobjOffsetY = -_shipPosY;
+ }
+
// Calculate ship direction indices for sprite selection
// Map mouse position to 5x7 direction grid (like Handler 7)
int16 mouseX = aimPos.x;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index a3acf396b74..4d125c67016 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -160,10 +160,11 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Font 0 (^f00): TALKFONT.NUT - Default menu font (DAT_00485058)
// Font 1 (^f01): SMALFONT.NUT - Small font for format code switching
// Font 2 (^f02): TITLFONT.NUT - Title font
- // Font 3 (^f03): POVFONT.NUT - POV font (not loaded here)
+ // Font 3 (^f03): POVFONT.NUT - POV font
_smush_talkfontNut = makeRebel2Font(_vm, "SYSTM/TALKFONT.NUT");
_smush_smalfontNut = makeRebel2Font(_vm, "SYSTM/SMALFONT.NUT");
_smush_titlefontNut = makeRebel2Font(_vm, "SYSTM/TITLFONT.NUT");
+ _smush_povfontNut = makeRebel2Font(_vm, "SYSTM/POVFONT.NUT");
_pauseOverlayActive = false;
memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
@@ -543,6 +544,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_gameplaySectionActive = false;
_lastGameplayMenuCloseTime = 0;
_lastMenuGamepadNavigationTime = 0;
+ _handler8HudGlyph = '#';
+ _handler8HudMessageTimer = 0;
+ _handler8HudMessageIndex = 0;
// Initialize level state tracking for multi-phase levels
_currentPhase = 1;
@@ -566,6 +570,7 @@ InsaneRebel2::~InsaneRebel2() {
delete _smush_talkfontNut;
delete _smush_smalfontNut;
delete _smush_titlefontNut;
+ delete _smush_povfontNut;
// Clean up Handler 8 ship sprites
delete _shipSprite;
@@ -1533,7 +1538,10 @@ int32 InsaneRebel2::processMouse() {
// Calculate world position for hit testing
Common::Point worldMousePos = mousePos;
- if (_rebelHandler != 7) {
+ if (_rebelHandler == 8) {
+ worldMousePos.x += _shipPosX;
+ worldMousePos.y += _shipPosY;
+ } else if (_rebelHandler != 7) {
worldMousePos.x += _viewX;
worldMousePos.y += _viewY;
}
@@ -1634,7 +1642,8 @@ int32 InsaneRebel2::processMouse() {
// Play enemy death sound.
// Pan based on enemy center X position: (screenX - 160) mapped to [-127,127]
{
- int enemyCenterX = (it->rect.left + it->rect.right) / 2 - _viewX;
+ int cameraX = (_rebelHandler == 8) ? _shipPosX : _viewX;
+ int enemyCenterX = (it->rect.left + it->rect.right) / 2 - cameraX;
int sfxPan = CLIP((enemyCenterX - 160) * 127 / 160, -127, 127);
if (_rebelHandler == 8 && it->type >= 1 && it->type <= 4) {
// Handler 8 soldier types 1-4: play from auxiliary buffer 0
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 54489204056..645596603c4 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -494,6 +494,7 @@ public:
NutRenderer *_smush_talkfontNut; // Font 0 - primary menu font (DAT_00485058)
NutRenderer *_smush_smalfontNut; // Font 1 - small font for ^f01 switching
NutRenderer *_smush_titlefontNut; // Font 2 - title font
+ NutRenderer *_smush_povfontNut; // Font 3 - POV font for Handler 8 overlay
// Saved palette for pause overlay restoration (FUN_405A21)
byte _savedPausePalette[768];
@@ -612,6 +613,7 @@ public:
// Update target lock state and draw crosshair/reticle
void renderCrosshair(byte *renderBitmap, int pitch, int width, int height);
+ void renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height);
Common::Point getHandler7ShipDrawPoint();
Common::Point getHandler7ProjectedPoint();
Common::Point getHandler7ShotTargetPoint();
@@ -1086,6 +1088,9 @@ public:
// Mode 4: "Autopilot" - no shooting, scripted movement
// Mode 5: "Cutscene" - ship not rendered
int16 _shipLevelMode; // DAT_0043e000
+ char _handler8HudGlyph; // DAT_0047e048
+ int16 _handler8HudMessageTimer; // DAT_0047e040
+ int16 _handler8HudMessageIndex; // DAT_0047e044
// Movement range limiter for Handler 8 (Level 2 covered/shooting states)
// Controls horizontal movement range: 127 for shooting, 41 for covered
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 97d32232cc0..9bdb7e7583a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -63,6 +63,66 @@ static int countEmbeddedFramePixels(const InsaneRebel2::EmbeddedSanFrame &frame)
return count;
}
+static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
+ NutRenderer **fonts, int numFonts, NutRenderer *defaultFont) {
+ int fontId = InsaneRebel2::parseFormatCode(str, curColor);
+ if (fontId >= 0) {
+ curFont = (fontId < numFonts && fonts[fontId]) ? fonts[fontId] : defaultFont;
+ return true;
+ }
+
+ return fontId == -2;
+}
+
+static const char *getHandler8PovOverlayString(int id) {
+ switch (id) {
+ case 200:
+ return "^f03^c248 MODE ^c005 INFARED";
+ case 201:
+ return "^f03^c248 SCANNING";
+ case 202:
+ return "^f03^c005 VHL EMM %lx";
+ case 203:
+ return "^f03^c005 SCOOBYNESS FACTOR %lx";
+ case 204:
+ return "^f03^c005 USELESS COORDINATES %lx %lx";
+ case 205:
+ return "^f03^c248 Buy Afterlife";
+ case 206:
+ return "^f03^c248 No Homework.. You must fight the bear";
+ case 207:
+ return "^f03^c248 What is that watermelon doing there?";
+ case 208:
+ return "^f03^c248 I just like to say taboo";
+ case 209:
+ return "^f03^c248 How about them Bears";
+ case 210:
+ return "^f03^c248 There is cause to be optimistic -HAL";
+ case 211:
+ return "^f03^c248 Little Darth is between his legs -LISA";
+ default:
+ return nullptr;
+ }
+}
+
+static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
+ if (max == 0)
+ return 0;
+ if (max < 0)
+ return -vm->_rnd.getRandomNumber(-max - 1);
+ return vm->_rnd.getRandomNumber(max - 1);
+}
+
+static void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
+ int pitch, int width, int height, const char *str, int x, int y, int16 color,
+ TextStyleFlags flags) {
+ if (!str)
+ return;
+
+ Common::Rect clipRect(0, 0, width, height);
+ drawRebel2String(fontSet, str, strlen(str), renderBitmap, clipRect, x, y, pitch, color, flags);
+}
+
static void blitEmbeddedFrameRegion(byte *renderBitmap, int pitch, int clipWidth, int clipHeight,
const InsaneRebel2::EmbeddedSanFrame &frame, int destX, int destY,
int srcX, int srcY, int drawWidth, int drawHeight) {
@@ -462,8 +522,8 @@ void InsaneRebel2::spawnVehicleShot(int x, int y) {
// FUN_0041189e(6, local_c + 1, 0x7f, 0, 0) â HBLAST.SAD
playSfx(6, 127, 0);
_vehicleShots[i].counter = getShotMaxDuration();
- _vehicleShots[i].targetX = x + _viewX;
- _vehicleShots[i].targetY = y + _viewY;
+ _vehicleShots[i].targetX = x;
+ _vehicleShots[i].targetY = y;
break;
}
}
@@ -2187,6 +2247,16 @@ void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int
// updatePostRenderScroll -- Set SmushPlayer scroll offsets for the current frame.
void InsaneRebel2::updatePostRenderScroll(int width, int height) {
+ if (_rebelHandler == 8) {
+ // Handler 8 follows FUN_00401234/FUN_00401CCF: the camera is applied
+ // through FUN_00424510 before FOBJ decoding, not by scrolling the final
+ // buffer copied by FUN_00424540.
+ _viewX = 0;
+ _viewY = 0;
+ _player->setScrollOffset(0, 0);
+ return;
+ }
+
// Rebel Assault 2 uses a buffer larger (424x260) than screen (320x200).
// Map mouse X (0-320) to Scroll X (0-104), and Y (0-200) to Scroll Y (0-60).
int maxScrollX = width - _vm->_screenWidth;
@@ -2529,6 +2599,9 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// Crosshair/reticle (FUN_004089ab, FUN_0040d836).
renderCrosshair(renderBitmap, pitch, width, height);
+ // Handler 8 POV text overlay (FUN_00401CCF).
+ renderHandler8PovOverlay(renderBitmap, pitch, width, height);
+
// HUD score/lives rendering (FUN_0041c012).
renderScoreHUD(renderBitmap, pitch, width, height, statusBarY);
@@ -2740,13 +2813,6 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
Common::Rect clipRect(0, 0, width, height);
- // Wrapper around shared parseFormatCode that also updates curFont
- auto parseFormat = [&](const char *&s, NutRenderer *&curFont, int &curColor) {
- int fc = parseFormatCode(s, curColor);
- if (fc >= 0) { curFont = (fonts[fc] ? fonts[fc] : defaultFont); return true; }
- return fc == -2;
- };
-
// Split into lines, then render each centered at textX (FUN_004341a0).
// Older RA2 text loading joined multi-line strings with spaces, leaving
// " ^f" as the separator; current TRES loading preserves real newlines.
@@ -2793,7 +2859,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
NutRenderer *mFont = defaultFont;
int mColor = 1;
while (s < lineEnd && (visCount + lineVisCount) < displayLen) {
- if (parseFormat(s, mFont, mColor))
+ if (parseRebel2TextOverlayFormat(s, mFont, mColor, fonts, ARRAYSIZE(fonts), defaultFont))
continue;
lineFont = mFont;
byte c = (byte)*s++;
@@ -2813,7 +2879,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
NutRenderer *curFont = defaultFont;
int curColor = 1;
while (s < lineEnd && (visCount + lineCharsDrawn) < displayLen) {
- if (parseFormat(s, curFont, curColor))
+ if (parseRebel2TextOverlayFormat(s, curFont, curColor, fonts, ARRAYSIZE(fonts), defaultFont))
continue;
byte c = (byte)*s++;
if (c >= 'a' && c <= 'z')
@@ -3285,8 +3351,8 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
// (centered for Level 2/11 third-person, bottom for Level 12 FPS).
int16 spriteXOffset = _shipSprite->getCharXOffset(spriteIndex);
int16 spriteYOffset = _shipSprite->getCharYOffset(spriteIndex);
- int drawX = displayOffsetX + spriteXOffset + _viewX;
- int drawY = displayOffsetY + spriteYOffset + _viewY;
+ int drawX = displayOffsetX + spriteXOffset;
+ int drawY = displayOffsetY + spriteYOffset;
renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _shipSprite, spriteIndex);
@@ -3297,8 +3363,8 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
if (shadowIndex < _shipSprite2->getNumChars()) {
int16 shadowXOff = _shipSprite2->getCharXOffset(shadowIndex);
int16 shadowYOff = _shipSprite2->getCharYOffset(shadowIndex);
- int shadowX = displayOffsetX + shadowXOff + _viewX;
- int shadowY = displayOffsetY + shadowYOff + _viewY;
+ int shadowX = displayOffsetX + shadowXOff;
+ int shadowY = displayOffsetY + shadowYOff;
renderNutSprite(renderBitmap, pitch, width, height, shadowX, shadowY, _shipSprite2, shadowIndex);
}
}
@@ -3307,7 +3373,7 @@ void InsaneRebel2::renderHandler8Ship(byte *renderBitmap, int pitch, int width,
int sprH = _shipSprite->getCharHeight(spriteIndex);
debug("Rebel2 Handler8: Ship at (%d,%d) raw(%d,%d) offset(%d,%d) nutOff(%d,%d) size(%d,%d) bottom=%d view(%d,%d) sprite=%d/%d",
drawX, drawY, _shipPosX, _shipPosY, displayOffsetX, displayOffsetY,
- spriteXOffset, spriteYOffset, sprW, sprH, drawY + sprH - _viewY,
+ spriteXOffset, spriteYOffset, sprW, sprH, drawY + sprH,
_viewX, _viewY, spriteIndex, numSprites);
}
@@ -3321,8 +3387,8 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
if (impact.counter <= 0)
continue;
- int drawX = impact.x - _shipPosX + _viewX;
- int drawY = impact.y - _shipPosY + _viewY;
+ int drawX = impact.x - _shipPosX;
+ int drawY = impact.y - _shipPosY;
// Original draws DAT_0047e020 repeatedly based on remaining life, then
// DAT_0047e018 once, both using the sampled background-mask sprite index.
@@ -3791,7 +3857,7 @@ void InsaneRebel2::renderTurretExplosions(byte *renderBitmap, int pitch, int wid
}
// renderVehicleExplosions -- Handler 8 on-foot explosion rendering (FUN_402696).
-// Position: world coords minus camera offset (DAT_0043e006/08 = _viewX/_viewY).
+// Position: world coords minus camera offset (DAT_0043e006/08 = _shipPosX/_shipPosY).
// Scale thresholds: <11, <21. No secondary NUT.
void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int width, int height) {
if (!_smush_iconsNut)
@@ -3802,8 +3868,8 @@ void InsaneRebel2::renderVehicleExplosions(byte *renderBitmap, int pitch, int wi
continue;
// FUN_402696 line 22-23: screenX = worldX - DAT_0043e006, screenY = worldY - DAT_0043e008
- int screenX = _explosions[i].x - _viewX;
- int screenY = _explosions[i].y - _viewY;
+ int screenX = _explosions[i].x - _shipPosX;
+ int screenY = _explosions[i].y - _shipPosY;
renderExplosionFrame(renderBitmap, pitch, width, height, _explosions[i],
screenX, screenY, kExplosionAdvanceAfterDraw);
}
@@ -4060,8 +4126,8 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
// From FUN_402ED0: widthScale=0x14(20), heightScale=8, thickness=4
// Parameters: gunX, gunY -> shipScreenX, shipScreenY (NOT the stored target!)
drawLaserBeam(renderBitmap, pitch, width, height,
- gunX + _viewX, gunY + _viewY,
- shipScreenX + _viewX, shipScreenY + _viewY,
+ gunX, gunY,
+ shipScreenX, shipScreenY,
progress, maxDuration, 20, 8, 4);
_vehicleShots[i].counter--;
@@ -4176,6 +4242,69 @@ void InsaneRebel2::renderHandler25LaserShots(byte *renderBitmap, int pitch, int
}
}
+// renderHandler8PovOverlay -- Draw Level 12 POV text overlay (FUN_00401CCF).
+void InsaneRebel2::renderHandler8PovOverlay(byte *renderBitmap, int pitch, int width, int height) {
+ if (_rebelHandler != 8 || !renderBitmap || !_smush_talkfontNut || !_smush_povfontNut)
+ return;
+
+ Rebel2FontSet fontSet;
+ fontSet.numFonts = 4;
+ fontSet.defaultFont = 0;
+ fontSet.fonts[0] = _smush_talkfontNut;
+ fontSet.fonts[1] = _smush_smalfontNut ? _smush_smalfontNut : _smush_talkfontNut;
+ fontSet.fonts[2] = _smush_titlefontNut ? _smush_titlefontNut : _smush_talkfontNut;
+ fontSet.fonts[3] = _smush_povfontNut;
+
+ ScummEngine_v7 *vm = (ScummEngine_v7 *)_vm;
+
+ // Original updates DAT_0047e048 with random(5)+0x23 when random(20)==0.
+ if (getHandler8PovOverlayRandom(vm, 20) == 0)
+ _handler8HudGlyph = (char)(getHandler8PovOverlayRandom(vm, 5) + 0x23);
+
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ getHandler8PovOverlayString(200), 10, 5, 1, kStyleAlignLeft);
+
+ char buffer[128];
+ Common::sprintf_s(buffer, "^f03&#$%c", _handler8HudGlyph);
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ buffer, 10, 150, 248, kStyleAlignLeft);
+
+ const char *text = getHandler8PovOverlayString(203);
+ if (text) {
+ Common::sprintf_s(buffer, text,
+ (unsigned long)(uint32)(int32)getHandler8PovOverlayRandom(vm, 20000));
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ buffer, 10, 170, 1, kStyleAlignLeft);
+ }
+
+ text = getHandler8PovOverlayString(202);
+ if (text) {
+ Common::sprintf_s(buffer, text,
+ (unsigned long)(uint32)(int32)getHandler8PovOverlayRandom(vm, -0xd50));
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ buffer, 220, 160, 1, kStyleAlignLeft);
+ }
+
+ text = getHandler8PovOverlayString(204);
+ if (text) {
+ Common::sprintf_s(buffer, text, (unsigned long)(uint32)(int32)(int16)_shipPosX,
+ (unsigned long)(uint32)(int32)(int16)_shipPosY);
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ buffer, 220, 170, 1, kStyleAlignLeft);
+ }
+
+ if (_handler8HudMessageTimer == 0) {
+ _handler8HudMessageIndex = getHandler8PovOverlayRandom(vm, 100);
+ if (_handler8HudMessageIndex < 7)
+ _handler8HudMessageTimer = 0x32;
+ } else {
+ _handler8HudMessageTimer--;
+ drawHandler8PovOverlayText(fontSet, renderBitmap, pitch, width, height,
+ getHandler8PovOverlayString(_handler8HudMessageIndex + 0xcd),
+ 200, 5, 1, kStyleAlignCenter);
+ }
+}
+
// renderCrosshair -- Draw crosshair/reticle at the current handler's aim point.
void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int height) {
// From FUN_0040d836 (Handler 7) line 167-168: crosshair only drawn when DAT_004437c0 == 2
@@ -4202,7 +4331,10 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
aimPos = getGameplayAimPoint();
}
Common::Point worldMousePos = aimPos;
- if (_rebelHandler != 7) {
+ if (_rebelHandler == 8) {
+ worldMousePos.x += _shipPosX;
+ worldMousePos.y += _shipPosY;
+ } else if (_rebelHandler != 7) {
worldMousePos.x += _viewX;
worldMousePos.y += _viewY;
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 3b0d660c7fb..2c43508df0d 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -43,11 +43,52 @@ namespace Scumm {
// FUN_00423880 is initialized at startup with a 400000-byte preload buffer.
constexpr int kRebel2LoadBufferSize = 400000;
+constexpr int kRebel2GameplaySurfaceWidth = 0x1a8;
+constexpr int kRebel2GameplaySurfaceHeight = 0x104;
bool isRebel2FullFrameDeltaCodec(int codec) {
return codec == SMUSH_CODEC_DELTA_BLOCKS || codec == SMUSH_CODEC_DELTA_GLYPHS;
}
+bool isRebel2GameplayActive(Insane *insane) {
+ if (insane == nullptr)
+ return false;
+
+ return static_cast<InsaneRebel2 *>(insane)->getHandler() != 0;
+}
+
+void smushDecodeRA2Uncompressed(byte *dst, const byte *src, int left, int top,
+ int width, int height, int dstPitch, int srcPitch, int dataSize,
+ int srcSkipX, int srcSkipY) {
+ if (dst == nullptr || src == nullptr || width <= 0 || height <= 0 ||
+ dstPitch <= 0 || srcPitch <= 0) {
+ return;
+ }
+
+ const int64 srcOffset = (int64)srcSkipY * srcPitch + srcSkipX;
+ if (srcOffset < 0 || (dataSize > 0 && srcOffset >= dataSize))
+ return;
+
+ if (dataSize > 0) {
+ const int64 bytesAfterOffset = (int64)dataSize - srcOffset;
+ if (bytesAfterOffset < width)
+ return;
+
+ const int maxRows = (int)((bytesAfterOffset - width) / srcPitch) + 1;
+ height = MIN(height, maxRows);
+ if (height <= 0)
+ return;
+ }
+
+ src += srcOffset;
+ dst += top * dstPitch + left;
+ while (height-- > 0) {
+ memcpy(dst, src, width);
+ src += srcPitch;
+ dst += dstPitch;
+ }
+}
+
// ---------------------------------------------------------------------------
// SmushPlayerRebel2 â construction / destruction
// ---------------------------------------------------------------------------
@@ -93,7 +134,9 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_loadReadOffset = 8; // Original starts reading at offset 8 (skips header)
_lastLoadChunkIdx = -1;
_loadStreamId = 0;
+ _ra2FrameSourceSkipX = 0;
_ra2FrameSourceSkipY = 0;
+ _ra2FrameObjectOriginalWidth = 0;
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
_ra2PendingAnimHeaderPalette = false;
@@ -670,6 +713,7 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
* Returns true when the dimensions are valid and updates _dst, _width, _height as needed.
*/
void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int width, int height) {
+ _ra2FrameObjectOriginalWidth = width;
_ra2FrameObjectSurfaceWidth = width;
_ra2FrameObjectSurfaceHeight = height;
@@ -682,15 +726,23 @@ void SmushPlayerRebel2::ra2PrepareFrameObjectSurface(int left, int top, int widt
}
bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
- // Rebel2 uses a special buffer for all non-matching frames.
- // Oversized full-frame delta codecs decode into a tightly packed surface
- // with their own width as pitch. Other codecs honor left/top and pitch.
+ // 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
+ // drawing their small FOBJ chunks directly onto the main screen.
const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
const int64 fobjSize64 = (int64)width * height;
int surfaceWidth = width;
int surfaceHeight = height;
- if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
+ const bool useGameplaySurface = isRebel2GameplayActive(_insane) &&
+ !isRebel2FullFrameDeltaCodec(codec) &&
+ (fobjSize64 > screenSize || _specialBuffer != nullptr);
+
+ if (useGameplaySurface) {
+ surfaceWidth = kRebel2GameplaySurfaceWidth;
+ surfaceHeight = kRebel2GameplaySurfaceHeight;
+ } else if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
surfaceWidth = MAX(surfaceWidth, _ra2FrameObjectSurfaceWidth);
surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
}
@@ -752,6 +804,12 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
bool SmushPlayerRebel2::ra2DecodeCodec(int codec, const uint8 *src, int left, int top,
int width, int height, int pitch, int dataSize) {
switch (codec) {
+ case SMUSH_CODEC_UNCOMPRESSED: {
+ const int sourcePitch = (_ra2FrameObjectOriginalWidth > 0) ? _ra2FrameObjectOriginalWidth : width;
+ smushDecodeRA2Uncompressed(_dst, src, left, top, width, height, pitch, sourcePitch,
+ dataSize, _ra2FrameSourceSkipX, _ra2FrameSourceSkipY);
+ return true;
+ }
case SMUSH_CODEC_LINE_UPDATE:
case SMUSH_CODEC_LINE_UPDATE2: {
const uint8 *adjustedSrc = smushSkipRLELines(src, dataSize, _ra2FrameSourceSkipY);
@@ -882,13 +940,23 @@ bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int he
bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, int &width, int &height, int pitch, int *srcSkipY) {
int sourceSkipY = 0;
+ const int adjustedLeft = left + _fobjOffsetX;
+ _ra2FrameSourceSkipX = (adjustedLeft < 0) ? -adjustedLeft : 0;
_ra2FrameSourceSkipY = 0;
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_SKIP_RLE || codec == SMUSH_CODEC_RA2_BOMP ||
+ codec == SMUSH_CODEC_UNCOMPRESSED) {
_ra2FrameSourceSkipY = sourceSkipY;
if (srcSkipY)
*srcSkipY = 0;
+ } else if (isRebel2FullFrameDeltaCodec(codec)) {
+ // Codec 37/47 streams are full-frame delta streams, not row-prefixed
+ // RLE data. The original FOBJ dispatcher applies FUN_00424510 offsets
+ // to the destination coordinates passed to FUN_0042cba0; it does not
+ // advance the compressed source by clipped scanlines.
+ if (srcSkipY)
+ *srcSkipY = 0;
} else if (srcSkipY) {
*srcSkipY = sourceSkipY;
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 35af253e0f6..87c64a12431 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -83,7 +83,9 @@ private:
int32 _loadReadOffset;
int16 _lastLoadChunkIdx;
int16 _loadStreamId;
+ int _ra2FrameSourceSkipX;
int _ra2FrameSourceSkipY;
+ int _ra2FrameObjectOriginalWidth;
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
bool _ra2PendingAnimHeaderPalette;
Commit: e0985e843eeb5e4e9cb075300c5f71036fc754f8
https://github.com/scummvm/scummvm/commit/e0985e843eeb5e4e9cb075300c5f71036fc754f8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-03T19:37:44+02:00
Commit Message:
SCUMM: RA2: removed some useless statics
Changed paths:
engines/scumm/insane/rebel2/render.cpp
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 9bdb7e7583a..5eae883192b 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -63,7 +63,7 @@ static int countEmbeddedFramePixels(const InsaneRebel2::EmbeddedSanFrame &frame)
return count;
}
-static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
+bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont, int &curColor,
NutRenderer **fonts, int numFonts, NutRenderer *defaultFont) {
int fontId = InsaneRebel2::parseFormatCode(str, curColor);
if (fontId >= 0) {
@@ -74,7 +74,7 @@ static bool parseRebel2TextOverlayFormat(const char *&str, NutRenderer *&curFont
return fontId == -2;
}
-static const char *getHandler8PovOverlayString(int id) {
+const char *getHandler8PovOverlayString(int id) {
switch (id) {
case 200:
return "^f03^c248 MODE ^c005 INFARED";
@@ -105,7 +105,7 @@ static const char *getHandler8PovOverlayString(int id) {
}
}
-static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
+int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
if (max == 0)
return 0;
if (max < 0)
@@ -113,7 +113,7 @@ static int getHandler8PovOverlayRandom(ScummEngine_v7 *vm, int max) {
return vm->_rnd.getRandomNumber(max - 1);
}
-static void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
+void drawHandler8PovOverlayText(const Rebel2FontSet &fontSet, byte *renderBitmap,
int pitch, int width, int height, const char *str, int x, int y, int16 color,
TextStyleFlags flags) {
if (!str)
More information about the Scummvm-git-logs
mailing list