[Scummvm-git-logs] scummvm master -> 50674c1a69af97ec6a98cb5296640baf66ca9153
neuromancer
noreply at scummvm.org
Tue Jun 9 13:22:12 UTC 2026
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
7dc5e27590 SCUMM: RA2: reset music after each mission
34e0d8d555 SCUMM: RA2: initial code to support high resolution mode, disabled by default
b05208d428 SCUMM: RA2: perspective correction in high resolution mode for L2
f00a3eb8de SCUMM: RA2: gameplay corrected for in high resolution mode for L3
9644e84561 SCUMM: RA2: implement damage recovery
74401bdb4d SCUMM: RA2: some features for yoda mode
50674c1a69 SCUMM: RA1: no damage mode
Commit: 7dc5e27590620f4864e3ecb7a257d00aa9ea4b14
https://github.com/scummvm/scummvm/commit/7dc5e27590620f4864e3ecb7a257d00aa9ea4b14
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: reset music after each mission
Changed paths:
engines/scumm/insane/rebel2/audio.cpp
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/runlevels.cpp
diff --git a/engines/scumm/insane/rebel2/audio.cpp b/engines/scumm/insane/rebel2/audio.cpp
index cddd81a69c0..989d93074ef 100644
--- a/engines/scumm/insane/rebel2/audio.cpp
+++ b/engines/scumm/insane/rebel2/audio.cpp
@@ -48,6 +48,14 @@ void InsaneRebel2::terminateAudio() {
_audio.terminate();
}
+void InsaneRebel2::resetVideoAudio() {
+ _audio.reset();
+
+ SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
+ if (splayer)
+ splayer->resetAudioTracks();
+}
+
// queueAudioData -- Queue raw PCM data for playback on a track.
// Creates the queuing stream on first use. RA2 audio is 8-bit unsigned mono.
void InsaneRebel2::queueAudioData(int trackIdx, uint8 *data, int32 size, int volume, int pan) {
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index a5d34e8b9fa..a90f85136cb 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -186,6 +186,7 @@ void InsaneRebel2::playMissionBriefing() {
// All wrapper functions (FUN_00417168/4171c5/417ab2/417327) add | 8 before calling FUN_0041f4d0.
void InsaneRebel2::playCinematic(const char *filename) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during cinematics
@@ -202,6 +203,7 @@ void InsaneRebel2::playVideoWithText(const char *filename, int textID, int textX
int fadeInFrame, int fadeOutFrame) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0;
@@ -274,6 +276,7 @@ void InsaneRebel2::playLevelBegin(int levelId) {
void InsaneRebel2::playLevelEnd(int levelId) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during end cinematic
@@ -294,6 +297,7 @@ void InsaneRebel2::playLevelEnd(int levelId) {
void InsaneRebel2::playLevelRetry(int levelId) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
@@ -314,6 +318,7 @@ void InsaneRebel2::playLevelRetry(int levelId) {
void InsaneRebel2::playLevelGameOver(int levelId) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during game over cinematic
@@ -381,6 +386,7 @@ void InsaneRebel2::playEndingSequence() {
void InsaneRebel2::playCreditsSequence() {
debug("Rebel2: Playing menu credits");
+ resetVideoAudio();
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
splayer->setCurVideoFlags(0x20);
@@ -721,6 +727,7 @@ Common::String InsaneRebel2::selectDeathVideoVariant(int levelId, int phase, int
void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // No status bar during death cinematic
@@ -749,6 +756,7 @@ void InsaneRebel2::playLevelDeathVariant(int levelId, int phase, int frame) {
void InsaneRebel2::playLevelRetryVariant(int levelId, int phase) {
restoreDamageFlashPalette();
+ resetVideoAudio();
_gameplaySectionActive = false;
_rebelHandler = 0;
_rebelStatusBarSprite = 0; // Reset for retry - will be set by IACT opcode 6 if needed
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 9df2a13101b..1ab92ca4a60 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -1312,6 +1312,9 @@ public:
// Terminate audio system
void terminateAudio();
+ // Reset streamed SAN audio at independent video boundaries.
+ void resetVideoAudio();
+
// Process audio dispatches - called from SmushPlayer when iMUSE is null
// This replaces the iMUSE audio path for RA2
void processAudioFrame(int16 feedSize);
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index ede78f398b2..2048677e5ea 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -199,6 +199,8 @@ bool InsaneRebel2::playLevelSegment(const char *filename, uint16 flags, bool rec
_gameplaySectionActive = true;
enableIOSGamepadController();
} else {
+ if (_gameplaySectionActive)
+ resetVideoAudio();
_gameplaySectionActive = false;
restoreIOSGamepadController();
}
Commit: 34e0d8d5556a5927b28a56c469f82905ec32056e
https://github.com/scummvm/scummvm/commit/34e0d8d5556a5927b28a56c469f82905ec32056e
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: initial code to support high resolution mode, disabled by default
Changed paths:
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/metaengine.cpp
engines/scumm/scumm.cpp
engines/scumm/smush/rebel/smush_player_ra2.cpp
engines/scumm/smush/rebel/smush_player_ra2.h
engines/scumm/smush/smush_player.cpp
engines/scumm/smush/smush_player.h
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 4d484fdb487..6487dd5528b 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -827,8 +827,9 @@ void InsaneRebel2::handleOpcode6Handler7(Common::SeekableReadStream &b, int16 pa
// Step 1: Raw mouse input as offset from screen center.
// DAT_0047a7e0 = mouseX - 160, DAT_0047a7e2 = mouseY - 100.
// Handler 7 applies DAT_0047a7fe to its local vertical input after clamping.
- const int16 mouseX = _vm->_mouse.x;
- const int16 mouseY = _vm->_mouse.y;
+ const Common::Point aimPos = getGameplayAimPoint();
+ const int16 mouseX = aimPos.x;
+ const int16 mouseY = aimPos.y;
int16 inputX = (int16)(mouseX - 160); // DAT_0047a7e0
int16 inputY = (int16)(mouseY - 100); // DAT_0047a7e2
@@ -1555,8 +1556,8 @@ void InsaneRebel2::iactRebel2Opcode6(byte *renderBitmap, Common::SeekableReadStr
// Handler-specific routing:
// Handler 7 (FLY): FLY NUT sprites via par4 (1, 2, 3, 11)
// Handler 8 (POV): POV NUT sprites via par3 (1, 3, 6, 7) or background via par4=5
-// Handler 0x26 (turret): Turret HUD NUT via par3 (1-4)
-// Handler 0x19: Mixed turret mode, similar to 0x26
+// Handler 0x26 (turret): Turret HUD NUT via par3/par4 (1-4)
+// Handler 0x19: Speeder bike GRD/HUD resources via par4
//
// ScummVM refactor helper for opcode 8 Handler 7 FLY loading, not a separate retail function.
bool InsaneRebel2::loadOpcode8Handler7FlySprites(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4) {
@@ -1738,11 +1739,14 @@ void InsaneRebel2::loadOpcode8EmbeddedAnim(byte *renderBitmap, Common::SeekableR
bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData, int32 animDataSize, int16 par3, int16 par4) {
bool handled = false;
- // Handler 0x26/0x19: Turret HUD Overlays.
- // FUN_00407fcb case 8: par3 1-4 for HUD NUT loading.
- if (!handled && (_rebelHandler == 0x26 || _rebelHandler == 0x19)) {
- if (par3 >= 1 && par3 <= 4) {
- handled = loadTurretHudOverlay(animData, animDataSize, par3);
+ // Handler 0x26: Turret HUD Overlays.
+ // FUN_00407fcb case 8: handler 0x26 uses par4 1-4 for HUD NUT loading.
+ // Some chunks use par3 for the same low/high selector.
+ if (!handled && _rebelHandler == 0x26) {
+ int hudSelector = (par4 >= 1 && par4 <= 4) ? par4 : par3;
+
+ if (hudSelector >= 1 && hudSelector <= 4) {
+ handled = loadTurretHudOverlay(animData, animDataSize, hudSelector);
}
}
@@ -1780,9 +1784,13 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
// Fallback: Embedded SAN HUD overlays.
// For other cases, load as embedded SAN frame to HUD overlay slots.
if (!handled) {
- // Skip high-res data (par3 == 2, 4).
- if (par3 == 2 || par3 == 4) {
- debug("Rebel2 Opcode 8: Skipping high-res HUD par3=%d", par3);
+ const bool highRes = isHiRes();
+ const bool highResHud = (par3 == 2 || par3 == 4);
+ const bool lowResHud = (par3 == 1 || par3 == 3);
+
+ if ((!highRes && highResHud) || (highRes && lowResHud)) {
+ debug("Rebel2 Opcode 8: Skipping %s HUD par3=%d while running in %s mode",
+ highResHud ? "high-res" : "low-res", par3, highRes ? "high-res" : "low-res");
handled = true;
} else {
// Determine userId: Handler 0x19 uses par3, others use par4.
@@ -2126,45 +2134,48 @@ bool InsaneRebel2::loadHandler7FlySprites(Common::SeekableReadStream &b, int64 r
return assigned;
}
-// loadTurretHudOverlay -- Handler 0x26/0x19 turret HUD loading (FUN_00407fcb case 8).
-bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 par3) {
- // Handler 0x26/0x19 turret HUD overlay loading - FUN_00407fcb case 8
+// loadTurretHudOverlay -- Handler 0x26 turret HUD loading (FUN_00407fcb case 8).
+bool InsaneRebel2::loadTurretHudOverlay(byte *animData, int32 size, int16 selector) {
+ // Handler 0x26 turret HUD overlay loading - FUN_00407fcb case 8
// Resolution-dependent loading:
- // par3 == 1: Low-res primary HUD (DAT_0047fe78 / _hudOverlayNut)
- // par3 == 2: High-res primary HUD (skip in 320x200 mode)
- // par3 == 3: Low-res secondary HUD (DAT_0047fe80 / _hudOverlay2Nut)
- // par3 == 4: High-res secondary HUD (skip in 320x200 mode)
+ // selector == 1: Low-res primary HUD (DAT_0047fe78 / _hudOverlayNut)
+ // selector == 2: High-res primary HUD (DAT_0047fe78 / _hudOverlayNut)
+ // selector == 3: Low-res secondary HUD (DAT_0047fe80 / _hudOverlay2Nut)
+ // selector == 4: High-res secondary HUD (DAT_0047fe80 / _hudOverlay2Nut)
if (!animData || size <= 0) {
return false;
}
- // ScummVM runs at 320x200 (low-res), skip high-res data
- if (par3 == 2 || par3 == 4) {
- debug("Rebel2 loadTurretHudOverlay: Skipping high-res HUD par3=%d (running in low-res mode)", par3);
- return true; // Successfully "handled" by skipping
+ const bool highRes = isHiRes();
+ const int primarySlot = highRes ? 2 : 1;
+ const int secondarySlot = highRes ? 4 : 3;
+
+ if (selector >= 1 && selector <= 4 && selector != primarySlot && selector != secondarySlot) {
+ debug("Rebel2 loadTurretHudOverlay: Skipping %s HUD selector=%d (running in %s mode)",
+ (selector == 2 || selector == 4) ? "high-res" : "low-res", selector,
+ highRes ? "high-res" : "low-res");
+ return true;
}
- if (par3 != 1 && par3 != 3) {
+ if (selector != primarySlot && selector != secondarySlot) {
return false; // Not a turret HUD slot
}
NutRenderer *newNut = makeRebel2SpriteFromData(_vm, animData, size);
if (!newNut || newNut->getNumChars() <= 0) {
- debug("Rebel2 loadTurretHudOverlay: NUT load failed for par3=%d", par3);
+ debug("Rebel2 loadTurretHudOverlay: NUT load failed for selector=%d", selector);
delete newNut;
return false;
}
- debug("Rebel2 loadTurretHudOverlay: Loaded turret HUD NUT par3=%d with %d sprites",
- par3, newNut->getNumChars());
+ debug("Rebel2 loadTurretHudOverlay: Loaded turret HUD NUT selector=%d with %d sprites",
+ selector, newNut->getNumChars());
- if (par3 == 1) {
- // Low-res primary HUD overlay
+ if (selector == primarySlot) {
delete _hudOverlayNut;
_hudOverlayNut = newNut;
- } else { // par3 == 3
- // Low-res secondary HUD overlay
+ } else {
delete _hudOverlay2Nut;
_hudOverlay2Nut = newNut;
}
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index a90f85136cb..b133a232415 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -416,10 +416,14 @@ void InsaneRebel2::warpGameplayMouseNow(int x, int y) {
if (eventMan)
eventMan->purgeMouseEvents();
- _vm->_mouse.x = x;
- _vm->_mouse.y = y;
- _vm->_system->warpMouse(_vm->_macScreen ? x * 2 : x,
- _vm->_macScreen ? y * 2 + 2 * _vm->_macScreenDrawOffset : y);
+ const int scale = isHiRes() ? 2 : 1;
+ const int physicalX = x * scale;
+ const int physicalY = y * scale;
+
+ _vm->_mouse.x = physicalX;
+ _vm->_mouse.y = physicalY;
+ _vm->_system->warpMouse(_vm->_macScreen ? physicalX * 2 : physicalX,
+ _vm->_macScreen ? physicalY * 2 + 2 * _vm->_macScreenDrawOffset : physicalY);
if (eventMan)
eventMan->purgeMouseEvents();
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index f82fe30354e..400928d1832 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -122,12 +122,11 @@ int InsaneRebel2::processMenuInput() {
int result = -1;
- // Menu item Y positions (low-res 320x200 mode):
- // From FUN_0041f5ae: baseY = numItems * -5 + 0x68
- // With 7 selectable items: 7 * -5 + 104 = 69
- // Items at Y = 69, 79, 89, 99, 109, 119, 129 with spacing of 10
- const int baseY = _menuItemCount * -5 + 0x68;
- const int itemSpacing = 10;
+ const bool highRes = isHiRes();
+ const int baseY = highRes ? (_menuItemCount * -5 + 0x5a) * 2 + 0x1c : _menuItemCount * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
+ const int itemHitTop = highRes ? 2 : 1;
+ const int itemHitHeight = highRes ? 18 : 10;
// Process events from the queue (populated by notifyEvent)
while (!_menuEventQueue.empty()) {
@@ -184,7 +183,7 @@ int InsaneRebel2::processMenuInput() {
_vm->_mouse.y = event.mouse.y;
for (int i = 0; i < _menuItemCount; i++) {
int itemY = baseY + i * itemSpacing;
- if (event.mouse.y >= itemY - 1 && event.mouse.y < itemY + 9) {
+ if (event.mouse.y >= itemY - itemHitTop && event.mouse.y < itemY - itemHitTop + itemHitHeight) {
_menuSelection = i;
result = _menuSelection;
debug("Menu: Item %d selected (mouse)", _menuSelection);
@@ -198,7 +197,8 @@ int InsaneRebel2::processMenuInput() {
int mouseY = event.mouse.y;
for (int i = 0; i < _menuItemCount; i++) {
int itemY = baseY + i * itemSpacing;
- if (mouseY >= itemY - 4 && mouseY < itemY + 6) {
+ if (mouseY >= itemY - itemHitTop - 3 * (highRes ? 2 : 1) &&
+ mouseY < itemY - itemHitTop + itemHitHeight - 3 * (highRes ? 2 : 1)) {
if (i != _menuSelection) {
_menuSelection = i;
debug(5, "Menu: Hover selection changed to %d (mouseY=%d)", _menuSelection, mouseY);
@@ -258,10 +258,13 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
// Item Y: param_3 * -5 + i * 10 + 0x68
// Box Y: param_3 * -5 + i * 10 + 0x67 (1px above text)
- const int centerX = width / 2;
- const int titleY = numItems * -5 + (leftAligned ? 0x56 : 0x51);
- const int itemBaseY = numItems * -5 + 0x68;
- const int itemSpacing = 10;
+ const bool highRes = isHiRes();
+ const int centerX = highRes ? 0x140 : width / 2;
+ const int titleY = highRes ?
+ (numItems * -5 + 0x5a) * 2 + (leftAligned ? -8 : -0x12) :
+ numItems * -5 + (leftAligned ? 0x56 : 0x51);
+ const int itemBaseY = highRes ? (numItems * -5 + 0x5a) * 2 + 0x1c : numItems * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
NutRenderer *fonts[3] = { _smush_talkfontNut, _smush_smalfontNut, _smush_titlefontNut };
NutRenderer *defaultFont = fonts[0] ? fonts[0] : _smush_smalfontNut;
@@ -286,7 +289,7 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
// -------------------------------------------------------------------
{
int titleWidth = getStringWidth(items[0]);
- int titleX = leftAligned ? 40 : (centerX - titleWidth / 2);
+ int titleX = leftAligned ? (highRes ? 0x50 : 0x28) : (centerX - titleWidth / 2);
drawString(items[0], titleX, titleY);
}
@@ -300,25 +303,25 @@ void InsaneRebel2::drawMenuItems(byte *renderBitmap, int pitch, int width, int h
const char *text = items[i + 1];
int textWidth = getStringWidth(text);
- int textX = leftAligned ? 23 : (centerX - textWidth / 2);
+ int textX = leftAligned ? (highRes ? 0x2e : 0x17) : (centerX - textWidth / 2);
drawString(text, textX, itemY);
// Selection highlight box - FUN_004292d0
if (i == selection) {
// Width: textWidth + ((DAT_0047a808 < 2) - 1 & 6) + 6 = textWidth + 6
- int bracketWidth = textWidth + 6;
+ int bracketWidth = textWidth + (highRes ? 12 : 6);
// Height: ((DAT_0047a808 < 2) - 1 & 10) + 10 = 10
- int bracketHeight = 10;
+ int bracketHeight = highRes ? 20 : 10;
// Flash color: (-((DAT_0047a7e4 & 1) == 0) & 8U) - 0x10
// bit0==0: 8-16=248(0xF8), bit0==1: 0-16=240(0xF0)
byte highlightColor = ((_vm->_system->getMillis() / 133) & 1) ? 248 : 240;
// Box position: Y = itemY - 1 (0x67 vs 0x68)
- int leftX = leftAligned ? 20 : (centerX - bracketWidth / 2);
+ int leftX = leftAligned ? (highRes ? 0x28 : 0x14) : (centerX - bracketWidth / 2);
int rightX = leftX + bracketWidth;
- int topY = itemY - 1;
- int bottomY = itemY + bracketHeight - 1;
+ int topY = highRes ? itemY - 2 : itemY - 1;
+ int bottomY = topY + bracketHeight - 1;
int screenW = _vm->_screenWidth;
int screenH = _vm->_screenHeight;
@@ -929,10 +932,14 @@ int InsaneRebel2::processChapterSelectInput() {
_vm->_mouse.x = event.mouse.x;
_vm->_mouse.y = event.mouse.y;
{
- int baseY = _chapterItemCount * -5 + 0x68;
+ const bool highRes = isHiRes();
+ const int baseY = highRes ? (_chapterItemCount * -5 + 0x5a) * 2 + 0x1c : _chapterItemCount * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
+ const int itemHitTop = highRes ? 2 : 1;
+ const int itemHitHeight = highRes ? 18 : 10;
for (int i = 0; i < _chapterItemCount; i++) {
- int itemY = baseY + i * 10;
- if (event.mouse.y >= itemY - 1 && event.mouse.y < itemY + 9) {
+ int itemY = baseY + i * itemSpacing;
+ if (event.mouse.y >= itemY - itemHitTop && event.mouse.y < itemY - itemHitTop + itemHitHeight) {
_chapterSelection = i;
_previewOffsetY = _chapterSelection * -50 + 75;
updateMenuVirtualKeyboard();
@@ -948,12 +955,17 @@ int InsaneRebel2::processChapterSelectInput() {
{
// Mouse hover changes highlight (original FUN_0041f5ae mouse mode).
// Item Y = numItems * -5 + i * 10 + 0x68
- int baseY = _chapterItemCount * -5 + 0x68;
+ const bool highRes = isHiRes();
+ const int baseY = highRes ? (_chapterItemCount * -5 + 0x5a) * 2 + 0x1c : _chapterItemCount * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
+ const int itemHitTop = highRes ? 2 : 1;
+ const int itemHitHeight = highRes ? 18 : 10;
int mouseY = event.mouse.y;
for (int i = 0; i < _chapterItemCount; i++) {
- int itemY = baseY + i * 10;
- if (mouseY >= itemY - 4 && mouseY < itemY + 6) {
+ int itemY = baseY + i * itemSpacing;
+ if (mouseY >= itemY - itemHitTop - 3 * (highRes ? 2 : 1) &&
+ mouseY < itemY - itemHitTop + itemHitHeight - 3 * (highRes ? 2 : 1)) {
if (i != _chapterSelection) {
_chapterSelection = i;
_previewOffsetY = _chapterSelection * -50 + 75;
@@ -1442,8 +1454,11 @@ int InsaneRebel2::processLevelSelectInput() {
if (itemCount <= 0)
return -1;
- const int itemBaseY = itemCount * -5 + 0x68;
- const int itemSpacing = 10;
+ const bool highRes = isHiRes();
+ const int itemBaseY = highRes ? (itemCount * -5 + 0x5a) * 2 + 0x1c : itemCount * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
+ const int itemHitTop = highRes ? 2 : 1;
+ const int itemHitHeight = highRes ? 18 : 10;
while (!_menuEventQueue.empty()) {
Common::Event event = _menuEventQueue.pop();
@@ -1494,7 +1509,7 @@ int InsaneRebel2::processLevelSelectInput() {
_vm->_mouse.y = event.mouse.y;
for (int i = 0; i < itemCount; i++) {
int itemY = itemBaseY + i * itemSpacing;
- if (event.mouse.y >= itemY - 1 && event.mouse.y < itemY + 9) {
+ if (event.mouse.y >= itemY - itemHitTop && event.mouse.y < itemY - itemHitTop + itemHitHeight) {
selection = i;
result = selection;
break;
@@ -1507,7 +1522,8 @@ int InsaneRebel2::processLevelSelectInput() {
_vm->_mouse.y = event.mouse.y;
for (int i = 0; i < itemCount; i++) {
int itemY = itemBaseY + i * itemSpacing;
- if (event.mouse.y >= itemY - 4 && event.mouse.y < itemY + 6) {
+ if (event.mouse.y >= itemY - itemHitTop - 3 * (highRes ? 2 : 1) &&
+ event.mouse.y < itemY - itemHitTop + itemHitHeight - 3 * (highRes ? 2 : 1)) {
selection = i;
break;
}
@@ -1759,52 +1775,54 @@ void InsaneRebel2::drawTopPilotsOverlay(byte *renderBitmap, int pitch, int width
if (!splayer)
return;
+ const int scale = isHiRes() ? 2 : 1;
+
// Title centered at X=152, Y=10 (TITLFONT)
- drawMenuStringCentered(renderBitmap, "^f02Top Pilots", 152, 10);
+ drawMenuStringCentered(renderBitmap, "^f02Top Pilots", 152 * scale, 10 * scale);
// Column headers at Y=30 (SMALFONT), positioned to match data columns
- int headerY = 30;
+ int headerY = 30 * scale;
int headerColor = 5;
- drawMenuStringCentered(renderBitmap, "^f01Rank", 43, headerY, headerColor);
- drawMenuString(renderBitmap, "^f01Name", 88, headerY, headerColor);
- drawMenuStringCentered(renderBitmap, "^f01Difficulty", 195, headerY, headerColor);
- drawMenuStringCentered(renderBitmap, "^f01Chapter", 245, headerY, headerColor);
- drawMenuStringRight(renderBitmap, "^f01Score", 295, headerY, headerColor);
+ drawMenuStringCentered(renderBitmap, "^f01Rank", 43 * scale, headerY, headerColor);
+ drawMenuString(renderBitmap, "^f01Name", 88 * scale, headerY, headerColor);
+ drawMenuStringCentered(renderBitmap, "^f01Difficulty", 195 * scale, headerY, headerColor);
+ drawMenuStringCentered(renderBitmap, "^f01Chapter", 245 * scale, headerY, headerColor);
+ drawMenuStringRight(renderBitmap, "^f01Score", 295 * scale, headerY, headerColor);
// Animated reveal: show up to _topPilotsFrameCount entries
int showCount = MIN(_topPilotsFrameCount, _numRankings);
for (int row = 0; row < showCount; row++) {
const RankingEntry &r = _rankings[row];
- int rowY = row * 10 + 42;
+ int rowY = (row * 10 + 42) * scale;
int color = 244; // 0xF4
// Column 1: Rank medals at X=43, centered (font 0 = TALKFONT)
Common::String rankStr = getRankString(r.rating);
if (!rankStr.empty()) {
Common::String rankFmt = Common::String::format("^f00%s", rankStr.c_str());
- drawMenuStringCentered(renderBitmap, rankFmt.c_str(), 43, rowY, color);
+ drawMenuStringCentered(renderBitmap, rankFmt.c_str(), 43 * scale, rowY, color);
}
// Column 2: Pilot name at X=88, left-aligned (font 1 = SMALFONT)
Common::String nameFmt = Common::String::format("^f01%s", r.name);
- drawMenuString(renderBitmap, nameFmt.c_str(), 88, rowY, color);
+ drawMenuString(renderBitmap, nameFmt.c_str(), 88 * scale, rowY, color);
// Column 3: Difficulty at X=195, centered - TRS (difficulty + 155)
int trsIdx = CLIP((int)r.difficulty, 0, 5) + 155;
const char *diffStr = splayer->getString(trsIdx);
if (diffStr && diffStr[0]) {
Common::String diffFmt = Common::String::format("^f01%s", diffStr);
- drawMenuStringCentered(renderBitmap, diffFmt.c_str(), 195, rowY, color);
+ drawMenuStringCentered(renderBitmap, diffFmt.c_str(), 195 * scale, rowY, color);
}
// Column 4: Highest chapter at X=245, centered
Common::String chFmt = Common::String::format("^f01%d", (int)r.chapter);
- drawMenuStringCentered(renderBitmap, chFmt.c_str(), 245, rowY, color);
+ drawMenuStringCentered(renderBitmap, chFmt.c_str(), 245 * scale, rowY, color);
// Column 5: Total score at X=295, right-aligned
Common::String scoreFmt = Common::String::format("^f01%ld", (long)r.score);
- drawMenuStringRight(renderBitmap, scoreFmt.c_str(), 295, rowY, color);
+ drawMenuStringRight(renderBitmap, scoreFmt.c_str(), 295 * scale, rowY, color);
}
_topPilotsFrameCount++;
@@ -1938,10 +1956,14 @@ int InsaneRebel2::processOptionsInput() {
if (event.type == Common::EVENT_LBUTTONDOWN) {
// Mouse click on items â match drawMenuItems Y positions
int mouseY = event.mouse.y;
- int baseY = _optionsItemCount * -5 + 0x68;
+ const bool highRes = isHiRes();
+ const int baseY = highRes ? (_optionsItemCount * -5 + 0x5a) * 2 + 0x1c : _optionsItemCount * -5 + 0x68;
+ const int itemSpacing = highRes ? 20 : 10;
+ const int itemHitTop = highRes ? 2 : 1;
+ const int itemHitHeight = highRes ? 18 : 10;
for (int i = 0; i < _optionsItemCount; i++) {
- int itemY = baseY + i * 10;
- if (mouseY >= itemY - 1 && mouseY < itemY + 9) {
+ int itemY = baseY + i * itemSpacing;
+ if (mouseY >= itemY - itemHitTop && mouseY < itemY - itemHitTop + itemHitHeight) {
_optionsSelection = i;
// Simulate enter for this item
Common::Event enterEvent;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index e356621903c..93c786740df 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -129,15 +129,14 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_smush_bencutNut = nullptr;
_smush_bensgoggNut = nullptr;
- // Rebel Assault 2 specific initialization can go here
+ const bool highRes = isHiRes();
- // Rebel Assault 2: Load cockpit sprites NUT which contains crosshairs, explosions, status bar
- // CPITIMAG.NUT = low-res (320x200), CPITIMHI.NUT = high-res (640x480)
- // The current renderer runs at 320x200, so use the low-res assets.
- _smush_iconsNut = new NutRenderer(_vm, "SYSTM/CPITIMAG.NUT");
+ // Rebel Assault 2: Load cockpit sprites NUT which contains crosshairs,
+ // explosions, reticles, and warning cues.
+ _smush_iconsNut = new NutRenderer(_vm, highRes ? "SYSTM/CPITIMHI.NUT" : "SYSTM/CPITIMAG.NUT");
_smush_icons2Nut = nullptr; // Not used for Rebel2
- // Initialize laser texture buffer (DAT_0047fee4) from sprite 5 of CPITIMAG.NUT
+ // Initialize laser texture buffer (DAT_0047fee4) from sprite 5 of CPITIMAG/CPITIMHI.NUT
// This is done by FUN_0040BAB0/FUN_0040BB87 in the original with sprite index 5
// Sprite 5 is 136x13 pixels - a wide, thin texture perfect for laser beams
_laserTexture.pixels = nullptr;
@@ -156,7 +155,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// >= 0: Edge highlights enabled, >= 1: high-detail (secondary NUTs, widescreen)
// Always use high detail in ScummVM.
_rebelDetailMode = 1;
- _smush_cockpitNut = new NutRenderer(_vm, "SYSTM/DISPFONT.NUT");
+ _smush_cockpitNut = new NutRenderer(_vm, highRes ? "SYSTM/DIHIFONT.NUT" : "SYSTM/DISPFONT.NUT");
// Load DIHIFONT.NUT for in-video messages/subtitles (Opcode 9)
_rebelMsgFont = makeRebel2Font(_vm, "SYSTM/DIHIFONT.NUT");
@@ -167,10 +166,10 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// 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
- _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");
+ _smush_talkfontNut = makeRebel2Font(_vm, highRes ? "SYSTM/TKHIFONT.NUT" : "SYSTM/TALKFONT.NUT");
+ _smush_smalfontNut = makeRebel2Font(_vm, highRes ? "SYSTM/SMHIFONT.NUT" : "SYSTM/SMALFONT.NUT");
+ _smush_titlefontNut = makeRebel2Font(_vm, highRes ? "SYSTM/TIHIFONT.NUT" : "SYSTM/TITLFONT.NUT");
+ _smush_povfontNut = makeRebel2Font(_vm, highRes ? "SYSTM/POHIFONT.NUT" : "SYSTM/POVFONT.NUT");
_pauseOverlayActive = false;
memset(_savedPausePalette, 0, sizeof(_savedPausePalette));
@@ -190,6 +189,8 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_noDamage = false;
_viewX = 0;
_viewY = 0;
+ _hiResPresentationViewX = 0;
+ _hiResPresentationViewY = 0;
// Damage visual effect counters (FUN_420515/420562/420754/42073B)
_damageFlashCounter = 0;
@@ -618,6 +619,10 @@ InsaneRebel2::~InsaneRebel2() {
}
}
+bool InsaneRebel2::isHiRes() const {
+ return _vm->_screenWidth >= 640 && _vm->_screenHeight >= 400;
+}
+
void InsaneRebel2::openGameplayMainMenu(SmushPlayer *splayer) {
if (!splayer)
return;
@@ -676,25 +681,25 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (_gameplayMouseSettleUntil != 0) {
const uint32 now = _vm->_system->getMillis();
if (now < _gameplayMouseSettleUntil) {
+ const int mouseScale = isHiRes() ? 2 : 1;
const int jumpX = event.mouse.x - _vm->_mouse.x;
const int jumpY = event.mouse.y - _vm->_mouse.y;
const bool largeAbsoluteJump =
- ABS(jumpX) >= kRA2Handler7MouseSettleJumpThreshold ||
- ABS(jumpY) >= kRA2Handler7MouseSettleJumpThreshold;
+ ABS(jumpX) >= kRA2Handler7MouseSettleJumpThreshold * mouseScale ||
+ ABS(jumpY) >= kRA2Handler7MouseSettleJumpThreshold * mouseScale;
const bool smallRelativeMove =
- ABS((int)event.relMouse.x) < kRA2Handler7MouseSettleRelativeThreshold &&
- ABS((int)event.relMouse.y) < kRA2Handler7MouseSettleRelativeThreshold;
+ ABS((int)event.relMouse.x) < kRA2Handler7MouseSettleRelativeThreshold * mouseScale &&
+ ABS((int)event.relMouse.y) < kRA2Handler7MouseSettleRelativeThreshold * mouseScale;
const bool nearWindowEdge =
- event.mouse.x <= kRA2Handler7MouseSettleEdgeMargin ||
- event.mouse.x >= kRA2GameplayMouseMaxX - kRA2Handler7MouseSettleEdgeMargin ||
- event.mouse.y <= kRA2Handler7MouseSettleEdgeMargin ||
- event.mouse.y >= kRA2GameplayMouseMaxY - kRA2Handler7MouseSettleEdgeMargin;
+ event.mouse.x <= kRA2Handler7MouseSettleEdgeMargin * mouseScale ||
+ event.mouse.x >= kRA2GameplayMouseMaxX * mouseScale - kRA2Handler7MouseSettleEdgeMargin * mouseScale ||
+ event.mouse.y <= kRA2Handler7MouseSettleEdgeMargin * mouseScale ||
+ event.mouse.y >= kRA2GameplayMouseMaxY * mouseScale - kRA2Handler7MouseSettleEdgeMargin * mouseScale;
if (largeAbsoluteJump && smallRelativeMove && nearWindowEdge) {
- const int recenterX = _vm->_mouse.x;
- const int recenterY = _vm->_mouse.y;
+ const Common::Point recenter = getGameplayAimPoint();
_gameplayMouseSettleUntil = now + kRA2Handler7MouseSettleExtendMs;
- warpGameplayMouseNow(recenterX, recenterY);
+ warpGameplayMouseNow(recenter.x, recenter.y);
debugC(DEBUG_INSANE, "Rebel2 H7 mouse settle: suppress pos=(%d,%d) rel=(%d,%d) current=(%d,%d) until=%u",
event.mouse.x, event.mouse.y, event.relMouse.x, event.relMouse.y,
@@ -1371,11 +1376,12 @@ void InsaneRebel2::renderScoreHUD(byte *renderBitmap, int pitch, int width, int
char scoreStr[16];
Common::sprintf_s(scoreStr, "%07d", _playerScore);
- // Score position from FUN_0041c012 assembly (low-res mode):
- // X = ((DAT_0047a808 < 2) - 1 & 0x101) + 0x101 = 0x101 = 257
- // Y = ((DAT_0047a808 < 2) - 1 & 4) + 4 = 4 (within status bar)
- int scoreX = 257 + _viewX;
- int scoreY = statusBarY + 4 + _viewY;
+ // Score position from FUN_0041c012 assembly:
+ // X = 0x101 low-res, 0x202 high-res
+ // Y = 4 low-res, 8 high-res (within status bar)
+ const int statusScale = isHiRes() ? 2 : 1;
+ int scoreX = 257 * statusScale + _viewX;
+ int scoreY = statusBarY + 4 * statusScale + _viewY;
// Render each digit as a NUT sprite (direct pixel blit with color 0 transparency).
// This matches the original's FUN_00434cb0 â FUN_004341a0 text rendering which
@@ -1772,12 +1778,19 @@ Common::Point InsaneRebel2::getGameplayAimPoint() {
// Pure getter (queried many times per frame): the aim/reticle follows the virtual
// mouse position. Directional controls pan that position incrementally once per frame
// via updateGameplayAimFromGamepad(), rather than snapping the reticle to a screen edge.
+ int x = _vm->_mouse.x;
int y = _vm->_mouse.y;
+ if (isHiRes()) {
+ x /= 2;
+ y /= 2;
+ }
+ x = CLIP<int>(x, 0, 319);
+ y = CLIP<int>(y, 0, 199);
if (_optControlsFlipped) {
// Original DAT_0047a7fe reverses only the up/down gameplay axis.
y = CLIP<int>(200 - y, 0, 199);
}
- return Common::Point(_vm->_mouse.x, y);
+ return Common::Point(x, y);
}
// Apply the user's configured analog deadzone so a resting stick reports no
@@ -1837,6 +1850,7 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
}
if (axisX || axisY || _gamepadAimActive) {
+ const Common::Point aimPos = getGameplayAimPoint();
const int centerX = 160;
const int centerY = 100;
const int absAxisX = ABS(axisX);
@@ -1850,8 +1864,8 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
centerY - curvedY * centerY / 127 :
centerY + curvedY * (199 - centerY) / 127;
const int maxStep = (axisX || axisY) ? 14 : 10;
- const int distX = targetX - _vm->_mouse.x;
- const int distY = targetY - _vm->_mouse.y;
+ const int distX = targetX - aimPos.x;
+ const int distY = targetY - aimPos.y;
if (distX || distY) {
deltaX = CLIP<int>(distX, -maxStep, maxStep);
@@ -1882,8 +1896,10 @@ void InsaneRebel2::updateGameplayAimFromGamepad() {
_gamepadAimActive = true;
// Integrate velocity into the reticle, clamped to the 320x200 play area.
- _vm->_mouse.x = (int16)CLIP<int>(_vm->_mouse.x + deltaX, 0, 319);
- _vm->_mouse.y = (int16)CLIP<int>(_vm->_mouse.y + deltaY, 0, 199);
+ Common::Point aimPos = getGameplayAimPoint();
+ const int scale = isHiRes() ? 2 : 1;
+ _vm->_mouse.x = (int16)(CLIP<int>(aimPos.x + deltaX, 0, 319) * scale);
+ _vm->_mouse.y = (int16)(CLIP<int>(aimPos.y + deltaY, 0, 199) * scale);
}
bool InsaneRebel2::isBitSet(int n) {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 1ab92ca4a60..f7a5490dbf8 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -538,6 +538,7 @@ public:
// Get current handler ID (8, 25, 38 etc.) for SMUSH player to query
int getHandler() const { return _rebelHandler; }
+ bool isHiRes() const;
void iactRebel2Scene1(byte *renderBitmap, int32 codecparam, int32 setupsan12,
int32 setupsan13, Common::SeekableReadStream &b, int32 size, int32 flags,
@@ -591,7 +592,7 @@ public:
void updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height);
void checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
- // Draw NUT-based HUD overlays for Handler 0x26/0x19 turret modes
+ // Draw NUT-based HUD overlays for Handler 0x26 turret modes
void renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
// Draw embedded SAN HUD overlays from IACT chunks
@@ -642,7 +643,7 @@ public:
bool loadHandler7ShotTable(Common::SeekableReadStream &b, int64 startPos, int64 remaining, int16 par4);
// Load turret HUD overlay NUT from ANIM data
- bool loadTurretHudOverlay(byte *animData, int32 size, int16 par3);
+ bool loadTurretHudOverlay(byte *animData, int32 size, int16 selector);
// Load Handler 8 ship POV NUT sprites from ANIM data (par4 = sprite type: 1,3,6,7)
bool loadHandler8ShipSprites(byte *animData, int32 size, int16 par4);
@@ -888,6 +889,8 @@ public:
int _viewX;
int _viewY;
+ int _hiResPresentationViewX;
+ int _hiResPresentationViewY;
// ---------------------------------------------------------------------------
// Damage Visual Effect System
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 7a84b815bff..50534d256e2 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -24,12 +24,14 @@
#include "common/util.h"
#include "graphics/cursorman.h"
+#include "graphics/managed_surface.h"
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
#include "scumm/smush/rebel/codec_ra2.h"
#include "scumm/smush/rebel/font_rebel2.h"
+#include "scumm/smush/rebel/smush_player_ra2.h"
#include "scumm/insane/rebel2/rebel.h"
@@ -184,12 +186,12 @@ void InsaneRebel2::renderEmbeddedFrame(byte *renderBitmap, const EmbeddedSanFram
// Render the decoded embedded frame to the video buffer
// Skip immediate draw for handlers that render HUD during post-processing:
// - Handler 7/8: Ship direction sprites selected based on direction
- // - Handler 0x26/0x19: Cockpit HUD positioned based on mouse/crosshair
+ // - Handler 0x26: Cockpit HUD positioned based on mouse/crosshair
//
// Exception: Handler 25 (0x19) background overlays (par4/userId=4, 6, 7) should draw immediately.
// These complete the visual scene and are NOT positioned by mouse/crosshair.
bool skipImmediateDraw = (_rebelHandler == 7 || _rebelHandler == 8 ||
- _rebelHandler == 0x26 || _rebelHandler == 0x19);
+ _rebelHandler == 0x26);
// Handler 25 overlays:
// - userId 4 (corridor overlay): draw immediately at the current view offset.
@@ -340,9 +342,9 @@ void InsaneRebel2::loadEmbeddedSan(int userId, byte *animData, int32 size, byte
debug("Rebel2: Embedded HUD frame: userId=%d, %dx%d at (%d,%d), codec=%d",
userId, width, height, left, top, codec);
- // Skip high-resolution frames - ScummVM runs at 320x200
- // If frame dimensions exceed low-res screen size, it's high-res data
- if (width > 400 || height > 250) {
+ // High-resolution HUD frames are used when the RA2 high-res option
+ // selects a 640x400 virtual screen. Keep skipping them in low-res mode.
+ if (!isHiRes() && (width > 400 || height > 250)) {
debug("Rebel2: SKIPPING high-res embedded frame: userId=%d, %dx%d (exceeds 400x250)",
userId, width, height);
stream.seek(nextSubPos);
@@ -1469,6 +1471,14 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
int16 texH = _laserTexture.height;
byte *texPixels = _laserTexture.pixels;
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ if (renderHiRes) {
+ gunX = (int16)((gunX - _hiResPresentationViewX) * 2);
+ gunY = (int16)((gunY - _hiResPresentationViewY) * 2);
+ targetX = (int16)((targetX - _hiResPresentationViewX) * 2);
+ targetY = (int16)((targetY - _hiResPresentationViewY) * 2);
+ }
+
// FUN_0040BBF6 line 23: sVar7 = (thickness * animFrame * 16) / maxFrames
if (maxFrames == 0)
maxFrames = 1;
@@ -1493,10 +1503,10 @@ void InsaneRebel2::drawLaserBeam(byte *dst, int pitch, int width, int height,
// Original callers pass a clip rect for the gameplay viewport (excluding status bar).
// This preserves texture phase at the viewport edge and avoids visibly "chopped" beams.
- int clipLeft = CLIP<int>(_viewX, 0, width - 1);
- int clipTop = CLIP<int>(_viewY, 0, height - 1);
- int clipRight = CLIP<int>(_viewX + 319, 0, width - 1);
- int clipBottom = CLIP<int>(_viewY + 179, 0, height - 1);
+ int clipLeft = renderHiRes ? 0 : CLIP<int>(_viewX, 0, width - 1);
+ int clipTop = renderHiRes ? 0 : CLIP<int>(_viewY, 0, height - 1);
+ int clipRight = renderHiRes ? MIN(width - 1, 639) : CLIP<int>(_viewX + 319, 0, width - 1);
+ int clipBottom = renderHiRes ? MIN(height - 1, 359) : CLIP<int>(_viewY + 179, 0, height - 1);
if (clipLeft > clipRight || clipTop > clipBottom)
return;
int edgeClipLeft = CLIP<int>(clipLeft + 1, 1, width - 2);
@@ -1695,8 +1705,9 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
// Calculate aim position in centered coordinates.
// Handler 0x26 applies the mouse-mode vertical inversion before DAT_0047a7fe
// (FUN_407FCB lines 108-123), so this must not use getGameplayAimPoint().
- const int rawX = _vm->_mouse.x - 160;
- const int rawY = _vm->_mouse.y - 100;
+ const Common::Point aimPos = getGameplayAimPoint();
+ const int rawX = aimPos.x - 160;
+ const int rawY = aimPos.y - 100;
int16 aimX = (int16)(rawX * 52 / 160);
int16 aimY = (int16)((_optControlsFlipped ? -rawY : rawY) * 45 / 100);
@@ -2189,6 +2200,75 @@ void renderNutSpriteClipped(byte *dst, int pitch, int dstH,
}
}
+static void renderNutSpriteScaledClipped(byte *dst, int pitch, int width, int height,
+ int clipLeft, int clipTop, int clipRight, int clipBottom,
+ int x, int y, NutRenderer *nut, int spriteIdx, bool mirror, int scale, bool transparent231) {
+ if (!nut || spriteIdx < 0 || spriteIdx >= nut->getNumChars() || !dst)
+ return;
+
+ if (scale < 1)
+ scale = 1;
+
+ const int dstW = MIN(width, pitch);
+ if (dstW <= 0 || height <= 0)
+ return;
+
+ if (clipLeft < 0)
+ clipLeft = 0;
+ if (clipTop < 0)
+ clipTop = 0;
+ if (clipRight > dstW)
+ clipRight = dstW;
+ if (clipBottom > height)
+ clipBottom = height;
+ if (clipLeft >= clipRight || clipTop >= clipBottom)
+ return;
+
+ int srcW = nut->getCharWidth(spriteIdx);
+ int srcH = nut->getCharHeight(spriteIdx);
+ const byte *src = nut->getCharData(spriteIdx);
+ if (!src || srcW <= 0 || srcH <= 0)
+ return;
+
+ const int scaledW = srcW * scale;
+ const int scaledH = srcH * scale;
+ int drawLeft = MAX(x, clipLeft);
+ int drawTop = MAX(y, clipTop);
+ int drawRight = MIN(x + scaledW, clipRight);
+ int drawBottom = MIN(y + scaledH, clipBottom);
+ if (drawLeft >= drawRight || drawTop >= drawBottom)
+ return;
+
+ if (!transparent231 && x >= clipLeft && y >= clipTop &&
+ x + scaledW <= clipRight && y + scaledH <= clipBottom) {
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ Graphics::Surface srcSurface;
+ srcSurface.init(srcW, srcH, srcW, const_cast<byte *>(src), format);
+
+ Graphics::ManagedSurface dstSurface;
+ dstSurface.surfacePtr()->init(dstW, height, pitch, dst, format);
+ dstSurface.transBlitFrom(srcSurface, Common::Rect(0, 0, srcW, srcH),
+ Common::Rect(x, y, x + scaledW, y + scaledH), 0, mirror);
+ return;
+ }
+
+ for (int dy = drawTop; dy < drawBottom; dy++) {
+ int srcY = (dy - y) / scale;
+ const byte *srcRow = src + srcY * srcW;
+ byte *dstRow = dst + dy * pitch;
+
+ for (int dx = drawLeft; dx < drawRight; dx++) {
+ int srcX = (dx - x) / scale;
+ if (mirror)
+ srcX = srcW - 1 - srcX;
+
+ byte px = srcRow[srcX];
+ if (px != 0 && (!transparent231 || px != 231))
+ dstRow[dx] = px;
+ }
+ }
+}
+
// renderNutSpriteMirrored -- NUT sprite with optional horizontal flip (FUN_004236e0).
void InsaneRebel2::renderNutSpriteMirrored(byte *dst, int pitch, int width, int height, int x, int y, NutRenderer *nut, int spriteIdx, bool mirror) {
if (!nut || spriteIdx < 0 || spriteIdx >= nut->getNumChars())
@@ -2249,6 +2329,13 @@ 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 == 0) {
+ _viewX = 0;
+ _viewY = 0;
+ _player->setScrollOffset(0, 0);
+ return;
+ }
+
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
@@ -2259,10 +2346,24 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
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;
- int maxScrollY = height - _vm->_screenHeight;
+ if (_rebelHandler == 25 && !isHiRes()) {
+ // Handler 25's low-res L2 corridor layers are authored for the 320x200
+ // viewport. The backing buffer may be larger to decode unclipped FOBJ
+ // data, but panning the final copy exposes unfilled columns/rows and
+ // breaks the corridor perspective.
+ _viewX = 0;
+ _viewY = 0;
+ _player->setScrollOffset(0, 0);
+ return;
+ }
+
+ // Rebel Assault 2 uses a native 320x200 viewport into buffers that may be
+ // larger (424x260). High-res mode still scrolls in those logical units; the
+ // selected viewport is promoted to 640x400 after native FOBJ decoding.
+ const int viewportWidth = isHiRes() ? 320 : _vm->_screenWidth;
+ const int viewportHeight = isHiRes() ? 200 : _vm->_screenHeight;
+ int maxScrollX = width - viewportWidth;
+ int maxScrollY = height - viewportHeight;
if (maxScrollX < 0)
maxScrollX = 0;
@@ -2271,8 +2372,8 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
// Simple linear mapping: Center of screen corresponds to center of buffer.
Common::Point aimPos = getGameplayAimPoint();
- _viewX = (aimPos.x * maxScrollX) / _vm->_screenWidth;
- _viewY = (aimPos.y * maxScrollY) / _vm->_screenHeight;
+ _viewX = (aimPos.x * maxScrollX) / viewportWidth;
+ _viewY = (aimPos.y * maxScrollY) / viewportHeight;
_player->setScrollOffset(_viewX, _viewY);
}
@@ -2526,6 +2627,24 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
// Original: FUN_00403240 only runs handlers when DAT_0047a814 == 0.
processMouse();
+ _hiResPresentationViewX = 0;
+ _hiResPresentationViewY = 0;
+ if (isHiRes()) {
+ SmushPlayerRebel2 *ra2Player = static_cast<SmushPlayerRebel2 *>(_player);
+ int nativeViewX = _viewX;
+ int nativeViewY = _viewY;
+ if (ra2Player && ra2Player->ra2PromoteCurrentFrameToHiRes(_viewX, _viewY)) {
+ renderBitmap = _player->_dst;
+ width = _player->_width;
+ height = _player->_height;
+ pitch = width;
+ _hiResPresentationViewX = nativeViewX;
+ _hiResPresentationViewY = nativeViewY;
+ _viewX = 0;
+ _viewY = 0;
+ }
+ }
+
// NOTE: Level 2 handler 8's background is restored in procPreRendering before
// SMUSH decodes the frame's FOBJ sprites. Handler 25 draws its corridor overlay
// from IACT opcode 6 instead. Redrawing either here would overwrite enemies.
@@ -2585,7 +2704,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
renderHandler25ShipPre(renderBitmap, pitch, width, height);
renderHandler25Ship(renderBitmap, pitch, width, height);
- // STEP 1A: Draw NUT-based HUD overlays for Handler 0x26/0x19 (FUN_004089ab lines 195-226).
+ // STEP 1A: Draw NUT-based HUD overlays for Handler 0x26 (FUN_004089ab lines 195-226).
// These are cockpit frame, crosshair, and reticle - drawn ON TOP of laser beams.
renderTurretHudOverlays(renderBitmap, pitch, width, height, curFrame);
@@ -2641,15 +2760,26 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
updatePostRenderScroll(width, height);
updatePostRenderDeath();
- // Use video content coordinates, NOT buffer coordinates
- const int videoWidth = 320; // Native video width
- const int videoHeight = 200; // Native video height
- const int statusBarY = 180; // 0xb4 - status bar starts at Y=180 in video coords
+ // Use video content coordinates, NOT oversized low-res gameplay-buffer coordinates.
+ const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
+ const int videoWidth = 320 * hudScale;
+ const int videoHeight = 200 * hudScale;
+ const int statusBarY = 180 * hudScale; // 0xb4 low-res, 0x168 high-res
// Hide HUD/status bar during intro videos (marked by SmushPlayer video flag 0x20)
// The 0x20 flag indicates a non-interactive cutscene/intro sequence OR menu
bool introPlaying = ((_player->_curVideoFlags & 0x20) != 0);
+ if (isHiRes() && _rebelHandler == 0) {
+ SmushPlayerRebel2 *ra2Player = static_cast<SmushPlayerRebel2 *>(_player);
+ if (ra2Player && ra2Player->ra2PromoteCurrentFrameToHiRes(0, 0)) {
+ renderBitmap = _player->_dst;
+ width = _player->_width;
+ height = _player->_height;
+ pitch = width;
+ }
+ }
+
if (handlePostRenderMenuModes(renderBitmap, pitch, width, height, introPlaying))
return;
@@ -2873,7 +3003,8 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
lines.push_back(cur);
}
- int drawY = _textOverlayY;
+ const int textScale = isHiRes() ? 2 : 1;
+ int drawY = _textOverlayY * textScale;
int visCount = 0;
for (uint lineIdx = 0; lineIdx < lines.size() && visCount < displayLen; lineIdx++) {
@@ -2902,7 +3033,7 @@ void InsaneRebel2::renderTextOverlay(byte *renderBitmap, int pitch, int width, i
}
// Draw line centered at textX
- int drawX = _textOverlayX - lineWidth / 2;
+ int drawX = _textOverlayX * textScale - lineWidth / 2;
int lineCharsDrawn = 0;
{
const char *s = lineStr;
@@ -2954,9 +3085,9 @@ void InsaneRebel2::renderStatusBarBackground(byte *renderBitmap, int pitch, int
}
}
-// renderTurretHudOverlays -- NUT-based HUD for Handler 0x26/0x19 (FUN_004089ab).
+// renderTurretHudOverlays -- NUT-based HUD for Handler 0x26 (FUN_004089ab).
void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
- // Draw NUT-based HUD overlays for Handler 0x26/0x19 (turret modes)
+ // Draw NUT-based HUD overlays for Handler 0x26 (turret modes)
// From FUN_004089ab disassembly (lines 195-226):
// - DAT_0047fe78 (_hudOverlayNut): Primary HUD overlay with 6 animation frames
// - Position formula (low-res):
@@ -2964,7 +3095,7 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
// Y = 182 - (mouseOffsetY >> 4) - height - spriteOffsetY
// - Animation: spriteIndex = (frameCounter / 2) % 6
- if ((_rebelHandler != 0x26 && _rebelHandler != 0x19) || !_hudOverlayNut || _hudOverlayNut->getNumChars() <= 0)
+ if (_rebelHandler != 0x26 || !_hudOverlayNut || _hudOverlayNut->getNumChars() <= 0)
return;
// Calculate mouse offset (clamped to -127..127)
@@ -2988,26 +3119,34 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
animFrame = (curFrame / 2) % animFrameCount;
}
- // Get sprite dimensions
- int spriteW = _hudOverlayNut->getCharWidth(animFrame);
- int spriteH = _hudOverlayNut->getCharHeight(animFrame);
+ const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
- // Position calculation from assembly (low-res mode)
- int spriteOffsetX = 0;
- int spriteOffsetY = 0;
- int hudX = 160 + (mouseOffsetX >> 4) - (spriteW / 2) - spriteOffsetX;
- int hudY = 182 - (mouseOffsetY >> 4) - spriteH - spriteOffsetY;
+ // FUN_004089ab computes the moving overlay anchor from sprite 0's dimensions
+ // and offsets, then FUN_004236e0 applies each rendered frame's own offsets.
+ const int baseSpriteW = _hudOverlayNut->getCharWidth(0);
+ const int baseSpriteH = _hudOverlayNut->getCharHeight(0);
+ const int baseSpriteXOff = _hudOverlayNut->getCharXOffset(0);
+ const int baseSpriteYOff = _hudOverlayNut->getCharYOffset(0);
+ const int horizontalTerm = (mouseOffsetX * hudScale) >> 4;
+ const int verticalInput = isHiRes() ? mouseOffsetY * 2 - 0x100 : mouseOffsetY - 0x80;
+ const int verticalTerm = verticalInput >> 4;
+ int hudX = 160 * hudScale + horizontalTerm - baseSpriteW / 2 - baseSpriteXOff;
+ int hudY = 182 * hudScale - verticalTerm - baseSpriteH - baseSpriteYOff;
// Apply view offset for scrolling background
hudX += _viewX;
hudY += _viewY;
// Draw base cockpit (sprite 0 always drawn first)
- renderNutSprite(renderBitmap, pitch, width, height, hudX, hudY, _hudOverlayNut, 0);
+ renderNutSprite(renderBitmap, pitch, width, height,
+ hudX + baseSpriteXOff, hudY + baseSpriteYOff, _hudOverlayNut, 0);
// Draw animation overlay frame if not frame 0
if (animFrame != 0 && animFrame < numSprites) {
- renderNutSprite(renderBitmap, pitch, width, height, hudX, hudY, _hudOverlayNut, animFrame);
+ renderNutSprite(renderBitmap, pitch, width, height,
+ hudX + _hudOverlayNut->getCharXOffset(animFrame),
+ hudY + _hudOverlayNut->getCharYOffset(animFrame),
+ _hudOverlayNut, animFrame);
}
debug(5, "Rebel2 HUD: Drawing NUT overlay frame %d/%d at (%d,%d) mouseOffset=(%d,%d)",
@@ -3017,8 +3156,8 @@ void InsaneRebel2::renderTurretHudOverlays(byte *renderBitmap, int pitch, int wi
if (_hudOverlay2Nut && _hudOverlay2Nut->getNumChars() > 0) {
int spr2W = _hudOverlay2Nut->getCharWidth(0);
int spr2H = _hudOverlay2Nut->getCharHeight(0);
- int hud2X = 160 + (mouseOffsetX >> 4) - (spr2W / 2) + _viewX;
- int hud2Y = 182 - (mouseOffsetY >> 4) - spr2H + _viewY;
+ int hud2X = 160 * hudScale + ((mouseOffsetX * hudScale) >> 4) - (spr2W / 2) + _viewX;
+ int hud2Y = 182 * hudScale - ((mouseOffsetY * hudScale) >> 4) - spr2H + _viewY;
renderNutSprite(renderBitmap, pitch, width, height, hud2X, hud2Y, _hudOverlay2Nut, 0);
}
}
@@ -3085,16 +3224,18 @@ void InsaneRebel2::renderEmbeddedHudOverlays(byte *renderBitmap, int pitch, int
int destX = frame.renderX;
int destY = frame.renderY;
- // Handler 0x26/0x19 turret positioning
- if ((_rebelHandler == 0x26 || _rebelHandler == 0x19) && (hudSlot == 1 || hudSlot == 2)) {
- destX = 160 - frame.width / 2 - frame.renderX;
- destY = 200 - frame.height - frame.renderY;
+ const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
+
+ // Handler 0x26 turret positioning
+ if (_rebelHandler == 0x26 && hudSlot >= 1 && hudSlot <= 4) {
+ destX = 160 * hudScale - frame.width / 2 - frame.renderX;
+ destY = 200 * hudScale - frame.height - frame.renderY;
}
// Handler 7 large cockpit frame positioning
if (_rebelHandler == 7 && (hudSlot == 1 || hudSlot == 2) && frame.width > 100) {
- destX = 160 - frame.width / 2 - frame.renderX;
- destY = 170 - frame.height - frame.renderY;
+ destX = 160 * hudScale - frame.width / 2 - frame.renderX;
+ destY = 170 * hudScale - frame.height - frame.renderY;
} else if (_rebelHandler == 7 && destX > 100 && destY > 50) {
int16 offsetX = (_shipPosX - 160) / 8;
int16 offsetY = (_shipPosY - 100) / 8;
@@ -3127,6 +3268,7 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
return;
int numSprites = _smush_cockpitNut->getNumChars();
+ const int statusScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
// --- Sprite 1: Status bar background (always drawn first as base layer) ---
if (numSprites > 1) {
@@ -3152,13 +3294,16 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
// If shield > 0xAA (170): alert blink with sprite 7
if (numSprites > 6) {
// Bar width from assembly: param_1 >> 2 (low-res)
- int damageBarWidth = _playerDamage >> 2;
+ int damageBarWidth = (_playerDamage * statusScale) >> 2;
const byte *src = _smush_cockpitNut->getCharData(6);
int sw = _smush_cockpitNut->getCharWidth(6);
int sh = _smush_cockpitNut->getCharHeight(6);
- const int dmgClipX = 63, dmgClipY = 9, dmgClipW = 64, dmgClipH = 6;
+ const int dmgClipX = 63 * statusScale;
+ const int dmgClipY = 9 * statusScale;
+ const int dmgClipW = 64 * statusScale;
+ const int dmgClipH = 6 * statusScale;
if (src && sw > 0 && sh > 0 && damageBarWidth > 0) {
int drawW = MIN(damageBarWidth, MIN(dmgClipW, sw - dmgClipX));
@@ -3223,12 +3368,16 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
int livesBarWidth = (_playerLives * 5 - 5) * 2;
if (livesBarWidth > 50)
livesBarWidth = 50;
+ livesBarWidth *= statusScale;
const byte *src = _smush_cockpitNut->getCharData(6);
int sw = _smush_cockpitNut->getCharWidth(6);
int sh = _smush_cockpitNut->getCharHeight(6);
- const int livClipX = 168, livClipY = 7, livClipW = 50, livClipH = 9;
+ const int livClipX = 168 * statusScale;
+ const int livClipY = 7 * statusScale;
+ const int livClipW = 50 * statusScale;
+ const int livClipH = 9 * statusScale;
if (src && sw > 0 && sh > 0 && livesBarWidth > 0) {
int drawW = MIN(livesBarWidth, MIN(livClipW, sw - livClipX));
@@ -3412,6 +3561,8 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
if (_rebelHandler != 8)
return;
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+
for (int i = 0; i < 7; i++) {
VehicleShotImpact &impact = _vehicleShotImpacts[i];
if (impact.counter <= 0)
@@ -3419,6 +3570,10 @@ void InsaneRebel2::renderVehicleShotImpacts(byte *renderBitmap, int pitch, int w
int drawX = impact.x - _shipPosX;
int drawY = impact.y - _shipPosY;
+ if (renderHiRes) {
+ drawX = (drawX - _hiResPresentationViewX) * 2;
+ drawY = (drawY - _hiResPresentationViewY) * 2;
+ }
// Original draws DAT_0047e020 repeatedly based on remaining life, then
// DAT_0047e018 once, both using the sampled background-mask sprite index.
@@ -3456,9 +3611,15 @@ 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 424x260), the status bar is at
- // Y = 180 + _viewY in buffer coordinates.
- int renderHeight = MIN(height, 180 + _viewY);
+ // In high-res presentation the low-res GRD sprite is scaled into the promoted
+ // 640x400 frame, so the gameplay clip becomes 360 pixels tall.
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int renderScale = renderHiRes ? 2 : 1;
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : 0;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : 0;
+ const int nativeBufferViewX = renderHiRes ? nativeViewX : _viewX;
+ const int nativeBufferViewY = renderHiRes ? nativeViewY : _viewY;
+ int renderHeight = renderHiRes ? MIN(height, 180 * renderScale) : MIN(height, 180 + _viewY);
// Draw _grd001Sprite based on _grdSpriteMode (DAT_00457900)
// Each mode has specific conditions from FUN_0041db5e:
@@ -3493,32 +3654,36 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
int16 spriteYOffset = _grd001Sprite->getCharYOffset(0);
// 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 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;
+ // Handler 25 stays viewport-locked in low-res mode, so _viewX/_viewY
+ // remain 0 even when the backing buffer is larger than 320x200.
+ // Other oversized-buffer modes scroll and need this compensation.
+ int nativeDrawX = _rebelViewOffset2X + spriteXOffset + nativeBufferViewX;
+ int nativeDrawY = _rebelViewOffset2Y + spriteYOffset + nativeBufferViewY;
+ int drawX = renderHiRes ? (nativeDrawX - nativeViewX) * renderScale : nativeDrawX;
+ int drawY = renderHiRes ? (nativeDrawY - nativeViewY) * renderScale : nativeDrawY;
// Apply half-width clipping from FUN_41DB5E:
// - mode1 uncovered: left half
// - mode4 uncovered: right half
int clipLeft = 0;
- int clipRight = width;
+ int clipRight = renderHiRes ? 320 : width;
if (useHalfWidth) {
- const int halfWidth = width / 2;
+ const int halfWidth = clipRight / 2;
clipLeft = useRightHalf ? halfWidth : 0;
clipRight = clipLeft + halfWidth;
}
+ int scaledClipLeft = clipLeft * renderScale;
+ int scaledClipRight = clipRight * renderScale;
// FUN_41DB5E mode-4 uncovered mutates DAT_00482230/34 (clip region),
// not DAT_00457910 (draw X). Keep drawX unchanged and clip only.
- renderNutSpriteClipped(renderBitmap, pitch, renderHeight,
- clipLeft, 0, clipRight, renderHeight,
- drawX, drawY, _grd001Sprite, 0);
+ renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
+ scaledClipLeft, 0, scaledClipRight, renderHeight,
+ drawX, drawY, _grd001Sprite, 0, false, renderScale, false);
- debug("Rebel2 Handler25 PRE: GRD001 at (%d,%d) nutOff(%d,%d) viewOff(%d,%d) size(%d,%d) mode=%d dmg=%d halfW=%d rightHalf=%d clip=[%d,%d)",
+ debug("Rebel2 Handler25 PRE: GRD001 at (%d,%d) nutOff(%d,%d) viewOff(%d,%d) size(%d,%d) mode=%d dmg=%d halfW=%d rightHalf=%d clip=[%d,%d) scale=%d",
drawX, drawY, spriteXOffset, spriteYOffset, _rebelViewOffset2X, _rebelViewOffset2Y,
- spriteW, spriteH, _grdSpriteMode, _rebelDamageLevel, useHalfWidth ? 1 : 0, useRightHalf ? 1 : 0, clipLeft, clipRight);
+ spriteW, spriteH, _grdSpriteMode, _rebelDamageLevel, useHalfWidth ? 1 : 0, useRightHalf ? 1 : 0, scaledClipLeft, scaledClipRight, renderScale);
}
}
@@ -3534,7 +3699,13 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
return;
// CRITICAL: Clip height to 180 (0xb4) + viewport Y to avoid drawing over status bar.
- int renderHeight = MIN(height, 180 + _viewY);
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int renderScale = renderHiRes ? 2 : 1;
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : 0;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : 0;
+ const int nativeBufferViewX = renderHiRes ? nativeViewX : _viewX;
+ const int nativeBufferViewY = renderHiRes ? nativeViewY : _viewY;
+ int renderHeight = renderHiRes ? MIN(height, 180 * renderScale) : MIN(height, 180 + _viewY);
// _grd002Sprite (GRD002) is always drawn if it exists (from FUN_41DB5E line 230)
// The sprite index is calculated based on damage level and aiming position
@@ -3642,26 +3813,30 @@ void InsaneRebel2::renderHandler25Ship(byte *renderBitmap, int pitch, int width,
int16 spriteXOffset = _grd002Sprite->getCharXOffset(spriteIdx);
int16 spriteYOffset = _grd002Sprite->getCharYOffset(spriteIdx);
- int drawX, drawY;
+ int nativeDrawX, nativeDrawY;
if (shouldMirror) {
// Mirrored position: X = DAT_00457910 + (320 - sprite_width - sprite_x_offset)
// From assembly lines 240-243
- drawX = _rebelViewOffset2X + (320 - spriteW - spriteXOffset) + _viewX;
+ nativeDrawX = _rebelViewOffset2X + (320 - spriteW - spriteXOffset) + nativeBufferViewX;
} else {
// Normal position: X = DAT_00457910 + sprite_internal_x_offset
// From assembly line 238
- drawX = _rebelViewOffset2X + spriteXOffset + _viewX;
+ nativeDrawX = _rebelViewOffset2X + spriteXOffset + nativeBufferViewX;
}
// Y = sprite_internal_y_offset + DAT_00457912
// From assembly line 246
- drawY = spriteYOffset + _rebelViewOffset2Y + _viewY;
+ nativeDrawY = spriteYOffset + _rebelViewOffset2Y + nativeBufferViewY;
+ int drawX = renderHiRes ? (nativeDrawX - nativeViewX) * renderScale : nativeDrawX;
+ int drawY = renderHiRes ? (nativeDrawY - nativeViewY) * renderScale : nativeDrawY;
- renderNutSpriteMirrored(renderBitmap, pitch, width, renderHeight, drawX, drawY, _grd002Sprite, spriteIdx, shouldMirror);
+ renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
+ 0, 0, width, renderHeight,
+ drawX, drawY, _grd002Sprite, spriteIdx, shouldMirror, renderScale, true);
- debug("Rebel2 Handler25: GRD002 at (%d,%d) nutOffset(%d,%d) viewOffset(%d,%d) size(%d,%d) spriteIdx=%d damage=%d dir=%d mirror=%d",
- drawX, drawY, spriteXOffset, spriteYOffset, _rebelViewOffset2X, _rebelViewOffset2Y, spriteW, spriteH, spriteIdx, _rebelDamageLevel, _rebelFlightDir, shouldMirror ? 1 : 0);
+ debug("Rebel2 Handler25: GRD002 at (%d,%d) nutOffset(%d,%d) viewOffset(%d,%d) size(%d,%d) spriteIdx=%d damage=%d dir=%d mirror=%d scale=%d",
+ drawX, drawY, spriteXOffset, spriteYOffset, _rebelViewOffset2X, _rebelViewOffset2Y, spriteW, spriteH, spriteIdx, _rebelDamageLevel, _rebelFlightDir, shouldMirror ? 1 : 0, renderScale);
}
}
@@ -3738,8 +3913,13 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
// so indicators stay aligned with decoded enemy sprites.
int fobjOffX = _player ? _player->_fobjOffsetX : 0;
int fobjOffY = _player ? _player->_fobjOffsetY : 0;
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int indicatorScale = renderHiRes ? 2 : getRebel2IndicatorScale(width, height);
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
+ const int nativeVideoWidth = renderHiRes ? videoWidth / indicatorScale : 320;
- Common::Rect viewRect(_viewX, _viewY, _viewX + videoWidth, _viewY + 200);
+ Common::Rect viewRect(nativeViewX, nativeViewY, nativeViewX + nativeVideoWidth, nativeViewY + 200);
const int sizeClamp = dparams.specialDamage; // DAT_0047e0fa in FUN_40A2E0
for (Common::List<enemy>::iterator it = _enemies.begin(); it != _enemies.end(); ++it) {
@@ -3784,8 +3964,14 @@ void InsaneRebel2::renderEnemyOverlays(byte *renderBitmap, int pitch, int width,
int centerY = r.top + halfH;
int iw = _smush_iconsNut->getCharWidth(spriteIndex);
int ih = _smush_iconsNut->getCharHeight(spriteIndex);
+ int drawX = centerX * indicatorScale - iw / 2;
+ int drawY = centerY * indicatorScale - ih / 2;
+ if (renderHiRes) {
+ drawX = (centerX - nativeViewX) * indicatorScale - iw / 2;
+ drawY = (centerY - nativeViewY) * indicatorScale - ih / 2;
+ }
renderNutSprite(renderBitmap, pitch, width, height,
- centerX - iw / 2, centerY - ih / 2, _smush_iconsNut, spriteIndex);
+ drawX, drawY, _smush_iconsNut, spriteIndex);
}
}
@@ -3858,8 +4044,15 @@ void InsaneRebel2::renderExplosionFrame(byte *renderBitmap, int pitch, int width
if (_smush_iconsNut->getNumChars() > spriteIndex) {
int ew = _smush_iconsNut->getCharWidth(spriteIndex);
int eh = _smush_iconsNut->getCharHeight(spriteIndex);
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ int drawX = screenX;
+ int drawY = screenY;
+ if (renderHiRes) {
+ drawX = (screenX - _hiResPresentationViewX) * 2;
+ drawY = (screenY - _hiResPresentationViewY) * 2;
+ }
renderNutSprite(renderBitmap, pitch, width, height,
- screenX - ew / 2, screenY - eh / 2, _smush_iconsNut, spriteIndex);
+ drawX - ew / 2, drawY - eh / 2, _smush_iconsNut, spriteIndex);
}
if (advance == kExplosionAdvanceAfterDraw)
@@ -3938,11 +4131,14 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
int numChars = _smush_iconsNut->getNumChars();
int spriteIndex = 0x15 - _hitCooldown; // 21 - remaining cooldown
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
if (spriteIndex >= 0 && spriteIndex < numChars) {
// Compute ship screen position (simplified FUN_0041c720 transform)
- int shipDrawX = (_flyShipScreenX - 0xd4) + _perspectiveX + 160 + _viewX;
- int shipDrawY = (_flyShipScreenY - 0x82) + _perspectiveY + 100 + _viewY;
+ int shipDrawX = (_flyShipScreenX - 0xd4) + _perspectiveX + 160 + nativeViewX;
+ int shipDrawY = (_flyShipScreenY - 0x82) + _perspectiveY + 100 + nativeViewY;
// Per-direction offset from ship center.
// Original uses lookup tables (DAT_004438da etc.) indexed by
@@ -3968,6 +4164,10 @@ void InsaneRebel2::renderSpaceExplosions(byte *renderBitmap, int pitch, int widt
int drawX = shipDrawX + offsetX;
int drawY = shipDrawY + offsetY;
+ if (renderHiRes) {
+ drawX = (drawX - nativeViewX) * 2;
+ drawY = (drawY - nativeViewY) * 2;
+ }
int ew = _smush_iconsNut->getCharWidth(spriteIndex);
int eh = _smush_iconsNut->getCharHeight(spriteIndex);
@@ -4025,6 +4225,9 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Uses pre-initialized _laserTexture from sprite 5 of CPITIMAG.NUT
int16 maxDuration = getShotMaxDuration();
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
for (int i = 0; i < 2; i++) {
if (_turretShots[i].counter <= 0)
@@ -4032,7 +4235,7 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Calculate sound panning from target X position (FUN_004262f0 call)
// sVar1 = ((2 - counter) * (targetX - 160)) / 2, clamped to [-127, 127]
- int16 pan = ((2 - _turretShots[i].counter) * (_turretShots[i].targetX - _viewX - 160)) / 2;
+ int16 pan = ((2 - _turretShots[i].counter) * (_turretShots[i].targetX - nativeViewX - 160)) / 2;
pan = CLIP<int16>(pan, -127, 127);
// TODO: Apply panning to sound channel i+1
@@ -4049,15 +4252,15 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Gun 2: (0xa0, 0x17c) = (160, 380) - center bottom (off-screen, clipped)
// Gun 3: (0x0a, 0xaa) = (10, 170) - left
drawLaserBeam(renderBitmap, pitch, width, height,
- 310 + _viewX, 170 + _viewY, targetX, targetY,
+ 310 + nativeViewX, 170 + nativeViewY, targetX, targetY,
progress, maxDuration, 12, 8, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 160 + _viewX, 380 + _viewY, targetX, targetY,
+ 160 + nativeViewX, 380 + nativeViewY, targetX, targetY,
progress, maxDuration, 8, 5, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 10 + _viewX, 170 + _viewY, targetX, targetY,
+ 10 + nativeViewX, 170 + nativeViewY, targetX, targetY,
progress, maxDuration, 12, 8, 12);
break;
@@ -4068,11 +4271,11 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Right: (0xd2, 0xe6) = (210, 230)
// Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
drawLaserBeam(renderBitmap, pitch, width, height,
- 110 + _viewX, 230 + _viewY, targetX, targetY,
+ 110 + nativeViewX, 230 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 210 + _viewX, 230 + _viewY, targetX, targetY,
+ 210 + nativeViewX, 230 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
break;
@@ -4082,11 +4285,11 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Gun 2: (0, 0)
// Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
drawLaserBeam(renderBitmap, pitch, width, height,
- -100 + _viewX, 0 + _viewY, targetX, targetY,
+ -100 + nativeViewX, 0 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 0 + _viewX, 0 + _viewY, targetX, targetY,
+ 0 + nativeViewX, 0 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
break;
@@ -4097,19 +4300,19 @@ void InsaneRebel2::renderTurretLaserShots(byte *renderBitmap, int pitch, int wid
// Assembly: widthScale=0x19(25), heightScale=8, thickness=0xC(12)
if ((_turretShots[i].seqNum & 1) == 0) {
drawLaserBeam(renderBitmap, pitch, width, height,
- 10 + _viewX, 50 + _viewY, targetX, targetY,
+ 10 + nativeViewX, 50 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 310 + _viewX, 130 + _viewY, targetX, targetY,
+ 310 + nativeViewX, 130 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
} else {
drawLaserBeam(renderBitmap, pitch, width, height,
- 310 + _viewX, 50 + _viewY, targetX, targetY,
+ 310 + nativeViewX, 50 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
drawLaserBeam(renderBitmap, pitch, width, height,
- 10 + _viewX, 130 + _viewY, targetX, targetY,
+ 10 + nativeViewX, 130 + nativeViewY, targetX, targetY,
progress, maxDuration, 25, 8, 12);
}
break;
@@ -4448,8 +4651,9 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
int ch = _smush_iconsNut->getCharHeight(reticleIndex);
// Calculate crosshair position
- int crosshairX = aimPos.x;
- int crosshairY = aimPos.y;
+ const int reticleScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
+ int crosshairX = aimPos.x * reticleScale;
+ int crosshairY = aimPos.y * reticleScale;
if (_rebelHandler != 7) {
crosshairX += _viewX;
crosshairY += _viewY;
@@ -4458,8 +4662,8 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
// Handler 25 (0x19): Add view offset to crosshair position
// From FUN_41DB5E lines 198-199: X = DAT_00457914 + DAT_0045790c, Y = DAT_00457916 + DAT_0045790e
if (_rebelHandler == 25) {
- crosshairX += _rebelViewOffsetX;
- crosshairY += _rebelViewOffsetY;
+ crosshairX += _rebelViewOffsetX * reticleScale;
+ crosshairY += _rebelViewOffsetY * reticleScale;
}
// FUN_004236e0 with flags=2 applies the NUT frame offsets, then centers.
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 8f899433929..0f75ecbec27 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -882,7 +882,7 @@ static const ExtraGuiOption enableRebel2HiRes = {
_s("High resolution mode"),
_s("Run the game in 640x400 high resolution mode instead of 320x200"),
"rebel2_hires",
- true,
+ false,
0,
0
};
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 7d19565b732..ba18b97a5bf 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -401,6 +401,9 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)
// #15666, #11290, and <https://forums.scummvm.org/viewtopic.php?p=97395#p97395>).
if (_game.id == GID_LOOM || !ConfMan.getBool("trim_fmtowns_to_200_pixels"))
_screenHeight = 240;
+ } else if (_game.id == GID_REBEL2 && ConfMan.getBool("rebel2_hires")) {
+ _screenWidth = 640;
+ _screenHeight = 400;
} else if (_game.version == 8 || _game.heversion >= 71) {
// COMI uses 640x480. Likewise starting from version 7.1, HE games use
// 640x480, too.
@@ -1826,7 +1829,7 @@ void ScummEngine_v7::setupScumm(const Common::Path &macResourceFile) {
if (_game.id == GID_REBEL2) {
_res->allocResTypeData(rtBuffer, 0, 10, kDynamicResTypeMode);
- initScreens(0, 200);
+ initScreens(0, _screenHeight);
_numVariables = 256;
_scummVars = (int32 *)calloc(_numVariables, sizeof(int32));
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.cpp b/engines/scumm/smush/rebel/smush_player_ra2.cpp
index 222b4914574..a4484f551c6 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra2.cpp
@@ -29,6 +29,8 @@
#include "common/rect.h"
#include "common/system.h"
+#include "graphics/blit.h"
+
#include "scumm/scumm.h"
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_font.h"
@@ -58,6 +60,30 @@ bool isRebel2GameplayActive(Insane *insane) {
return static_cast<InsaneRebel2 *>(insane)->getHandler() != 0;
}
+static void scaleNativeViewportToHiRes(byte *dst, int dstPitch, int dstWidth, int dstHeight,
+ const byte *src, int srcPitch, int srcWidth, int srcHeight, int scrollX, int scrollY) {
+ if (!dst || !src || dstPitch <= 0 || dstWidth < 640 || dstHeight < 400 ||
+ srcPitch <= 0 || srcWidth <= 0 || srcHeight <= 0)
+ return;
+
+ memset(dst, 0, (size_t)dstPitch * dstHeight);
+
+ scrollX = CLIP<int>(scrollX, 0, MAX<int>(0, srcWidth - 1));
+ scrollY = CLIP<int>(scrollY, 0, MAX<int>(0, srcHeight - 1));
+
+ const int outWidth = MIN<int>(320, dstWidth / 2);
+ const int outHeight = MIN<int>(200, dstHeight / 2);
+ const int srcViewWidth = MIN<int>(outWidth, srcWidth - scrollX);
+ const int srcViewHeight = MIN<int>(outHeight, srcHeight - scrollY);
+ if (srcViewWidth <= 0 || srcViewHeight <= 0)
+ return;
+
+ const byte *srcView = src + scrollY * srcPitch + scrollX;
+ Graphics::scaleBlit(dst, srcView, dstPitch, srcPitch,
+ srcViewWidth * 2, srcViewHeight * 2, srcViewWidth, srcViewHeight,
+ Graphics::PixelFormat::createFormatCLUT8());
+}
+
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) {
@@ -141,6 +167,10 @@ void SmushPlayerRebel2::initGamePlayerFields() {
_ra2FrameObjectOriginalWidth = 0;
_ra2FrameObjectSurfaceWidth = 0;
_ra2FrameObjectSurfaceHeight = 0;
+ _ra2LowResVideoBuffer = nullptr;
+ _ra2LowResVideoBufferSize = 0;
+ _ra2NativeFrameNeedsClear = false;
+ _ra2UsingGameplaySurface = false;
_ra2PendingAnimHeaderPalette = false;
memset(_ra2Codec45Palette, 0, sizeof(_ra2Codec45Palette));
memset(_ra2Codec45Lookup, 0, sizeof(_ra2Codec45Lookup));
@@ -157,6 +187,9 @@ void SmushPlayerRebel2::destroyGamePlayerFields() {
_lastFobjData = nullptr;
free(_loadBuffer);
_loadBuffer = nullptr;
+ free(_ra2LowResVideoBuffer);
+ _ra2LowResVideoBuffer = nullptr;
+ _ra2LowResVideoBufferSize = 0;
}
void SmushPlayerRebel2::ra2InitAudioTrackSizes() {
@@ -183,6 +216,7 @@ void SmushPlayerRebel2::ra2InitAudioTrackSizes() {
*/
void SmushPlayerRebel2::initGameVideoState() {
_ra2PendingAnimHeaderPalette = false;
+ _ra2UsingGameplaySurface = false;
// Re-push the SMUSH palette to the system. Videos like O_LEVEL.SAN
// have no NPAL chunk and inherit the palette from the previous video.
@@ -215,11 +249,81 @@ void SmushPlayerRebel2::releaseGameVideoState() {
_lastFobjData = nullptr;
_lastFobjDataSize = 0;
_hasFrameFobjForGost = false;
+ _ra2NativeFrameNeedsClear = false;
// 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.
}
+bool SmushPlayerRebel2::ra2IsHighResMode() const {
+ return _vm->_screenWidth >= 640 && _vm->_screenHeight >= 400;
+}
+
+bool SmushPlayerRebel2::ra2EnsureLowResVideoBuffer() {
+ const int bufSize = 320 * 200;
+ if (_ra2LowResVideoBuffer != nullptr && _ra2LowResVideoBufferSize >= bufSize)
+ return true;
+
+ byte *newBuffer = (byte *)realloc(_ra2LowResVideoBuffer, bufSize);
+ if (!newBuffer) {
+ warning("SmushPlayerRebel2::ra2EnsureLowResVideoBuffer: Failed to allocate %d-byte decode buffer", bufSize);
+ free(_ra2LowResVideoBuffer);
+ _ra2LowResVideoBuffer = nullptr;
+ _ra2LowResVideoBufferSize = 0;
+ return false;
+ }
+
+ _ra2LowResVideoBuffer = newBuffer;
+ _ra2LowResVideoBufferSize = bufSize;
+ memset(_ra2LowResVideoBuffer, 0, _ra2LowResVideoBufferSize);
+ return true;
+}
+
+void SmushPlayerRebel2::ra2ClearCurrentTarget() {
+ if (!_dst)
+ return;
+
+ int clearSize = 0;
+ if (_dst == _specialBuffer && _width > 0 && _height > 0) {
+ const int64 size64 = (int64)_width * _height;
+ if (size64 <= INT_MAX && size64 <= _specialBufferSize)
+ clearSize = (int)size64;
+ } else if (_dst == _ra2LowResVideoBuffer && _width > 0 && _height > 0) {
+ const int64 size64 = (int64)_width * _height;
+ if (size64 <= INT_MAX && size64 <= _ra2LowResVideoBufferSize)
+ clearSize = (int)size64;
+ } else {
+ clearSize = _vm->_screenWidth * _vm->_screenHeight;
+ }
+
+ if (clearSize > 0)
+ memset(_dst, 0, clearSize);
+}
+
+bool SmushPlayerRebel2::ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY) {
+ if (!ra2IsHighResMode() || !_dst || _width <= 0 || _height <= 0)
+ return false;
+
+ VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
+ byte *screen = vs->getPixels(0, 0);
+ if (_dst == screen && _width == _vm->_screenWidth && _height == _vm->_screenHeight)
+ return false;
+
+ const byte *src = _dst;
+ const int srcPitch = _width;
+ const int srcWidth = _width;
+ const int srcHeight = _height;
+
+ scaleNativeViewportToHiRes(screen, _vm->_screenWidth, _vm->_screenWidth, _vm->_screenHeight,
+ src, srcPitch, srcWidth, srcHeight, scrollX, scrollY);
+
+ _dst = screen;
+ _width = _vm->_screenWidth;
+ _height = _vm->_screenHeight;
+ setScrollOffset(0, 0);
+ return true;
+}
+
/**
* RA2-specific FTCH handling.
* For Handler 25, skips FTCH to preserve overlays.
@@ -355,24 +459,31 @@ bool SmushPlayerRebel2::handleGameTextRendering(const char *str, int fontId, int
}
SmushFont *SmushPlayerRebel2::getGameFont(int font) {
- // Font table for low-res mode (320x200). Original exe uses pointer
- // arithmetic to select hi/lo font pairs (e.g. TKHIFONT+0x14=TALKFONT).
+ // Original exe uses pointer arithmetic to select hi/lo font pairs.
// Font 0: TALKFONT (TKHIFONT hi-res)
// Font 1: SMALFONT (SMHIFONT hi-res)
// Font 2: TITLFONT (TIHIFONT hi-res)
// Font 3: POVFONT (POHIFONT hi-res)
- const char *ra2_fonts[] = {
+ const char *ra2FontsLo[] = {
"SYSTM/TALKFONT.NUT",
"SYSTM/SMALFONT.NUT",
"SYSTM/TITLFONT.NUT",
"SYSTM/POVFONT.NUT"
};
- int numFonts = ARRAYSIZE(ra2_fonts);
+ const char *ra2FontsHi[] = {
+ "SYSTM/TKHIFONT.NUT",
+ "SYSTM/SMHIFONT.NUT",
+ "SYSTM/TIHIFONT.NUT",
+ "SYSTM/POHIFONT.NUT"
+ };
+ const bool highRes = _vm->_screenWidth >= 640 && _vm->_screenHeight >= 400;
+ const char **ra2Fonts = highRes ? ra2FontsHi : ra2FontsLo;
+ int numFonts = ARRAYSIZE(ra2FontsLo);
if (font >= 0 && font < numFonts) {
- _sf[font] = new SmushFont(_vm, ra2_fonts[font], true);
+ _sf[font] = new SmushFont(_vm, ra2Fonts[font], true);
} else {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::getGameFont: RA2 unknown font %d, using TALKFONT", font);
- _sf[font] = new SmushFont(_vm, ra2_fonts[0], true);
+ _sf[font] = new SmushFont(_vm, ra2Fonts[0], true);
}
return _sf[font];
}
@@ -716,6 +827,13 @@ void SmushPlayerRebel2::ra2HandleTextResource(const char *str, int fontId, int c
int width, int height, TextStyleFlags flg) {
ensureMultiFont();
_multiFont->setDefaultFont(fontId);
+ const int scale = (_vm->_screenWidth >= 640 && _vm->_screenHeight >= 400) ? 2 : 1;
+ pos_x *= scale;
+ pos_y *= scale;
+ left *= scale;
+ top *= scale;
+ width *= scale;
+ height *= scale;
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2HandleTextResource: RA2 TRES frame=%d fontId=%d color=%d flags=0x%x pos=(%d,%d) clip=(%d,%d,%d,%d) str=\"%.40s\"",
_frame, fontId, color, (int)flg, pos_x, pos_y, left, top, width, height, str);
@@ -748,8 +866,25 @@ 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) {
+ const bool highRes = ra2IsHighResMode();
+ const bool gameplayActive = isRebel2GameplayActive(_insane);
+ if (highRes && (!gameplayActive || !_ra2UsingGameplaySurface || _specialBuffer == nullptr)) {
+ if (!ra2EnsureLowResVideoBuffer())
+ return false;
+ _dst = _ra2LowResVideoBuffer;
+ _width = 320;
+ _height = 200;
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using low-res decode buffer for high-res codec 45 mask");
+ } else if (_specialBuffer != nullptr) {
_dst = _specialBuffer;
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using _specialBuffer for codec 45 mask");
} else {
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
@@ -760,26 +895,75 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
}
// 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.
+ // High-res presentation still decodes video into native 320x200/424x260
+ // surfaces, then promotes the selected viewport to the 640x400 screen.
const int screenSize = _vm->_screenWidth * _vm->_screenHeight;
+ const int nativeScreenSize = 320 * 200;
const int64 fobjSize64 = (int64)width * height;
int surfaceWidth = width;
int surfaceHeight = height;
- const bool useGameplaySurface = isRebel2GameplayActive(_insane) &&
- !isRebel2FullFrameDeltaCodec(codec) &&
- (fobjSize64 > screenSize || _specialBuffer != nullptr);
+ const bool highRes = ra2IsHighResMode();
+ const bool gameplayActive = isRebel2GameplayActive(_insane);
+ const bool fullFrameDelta = isRebel2FullFrameDeltaCodec(codec);
+
+ if (!gameplayActive && fobjSize64 <= nativeScreenSize) {
+ if (highRes) {
+ if (!ra2EnsureLowResVideoBuffer())
+ return false;
+ _dst = _ra2LowResVideoBuffer;
+ _width = 320;
+ _height = 200;
+ } else {
+ VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
+ _dst = vs->getPixels(0, 0);
+ _width = _vm->_screenWidth;
+ _height = _vm->_screenHeight;
+ }
+
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using native screen target for menu/cinematic FOBJ %dx%d",
+ width, height);
+ return true;
+ }
+
+ const bool oversizedNative = fobjSize64 > nativeScreenSize ||
+ width > 320 || height > 200 ||
+ _ra2FrameObjectSurfaceWidth > 320 || _ra2FrameObjectSurfaceHeight > 200;
+ const bool useGameplaySurface = gameplayActive && !fullFrameDelta &&
+ (oversizedNative || _ra2UsingGameplaySurface);
if (useGameplaySurface) {
surfaceWidth = kRebel2GameplaySurfaceWidth;
surfaceHeight = kRebel2GameplaySurfaceHeight;
- } else if (fobjSize64 > screenSize && !isRebel2FullFrameDeltaCodec(codec)) {
+ _ra2UsingGameplaySurface = true;
+ } else if (!highRes && fobjSize64 > screenSize && !fullFrameDelta) {
surfaceWidth = MAX(surfaceWidth, _ra2FrameObjectSurfaceWidth);
surfaceHeight = MAX(surfaceHeight, _ra2FrameObjectSurfaceHeight);
}
+ if (highRes && !useGameplaySurface && !oversizedNative) {
+ if (width <= 0 || height <= 0) {
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Skipping invalid FOBJ dimensions %dx%d", width, height);
+ return false;
+ }
+ if (!ra2EnsureLowResVideoBuffer())
+ return false;
+ _dst = _ra2LowResVideoBuffer;
+ _width = 320;
+ _height = 200;
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
+ debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using low-res decode buffer for high-res FOBJ %dx%d",
+ width, height);
+ return true;
+ }
+
const int64 bufSize64 = (int64)surfaceWidth * surfaceHeight;
if (width <= 0 || height <= 0 || surfaceWidth <= 0 || surfaceHeight <= 0 || bufSize64 > INT_MAX) {
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Skipping invalid FOBJ dimensions %dx%d", width, height);
@@ -787,8 +971,9 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
}
const int bufSize = (int)bufSize64;
- if (bufSize > screenSize) {
- // Frame is larger than screen - need special buffer
+ const bool needsSpecialBuffer = useGameplaySurface || oversizedNative || bufSize > screenSize;
+ if (needsSpecialBuffer) {
+ // Frame is larger than the native target - need special buffer.
if (_specialBuffer == nullptr || bufSize > _specialBufferSize) {
byte *newSpecialBuffer = (byte *)malloc(bufSize);
if (newSpecialBuffer == nullptr) {
@@ -808,9 +993,13 @@ bool SmushPlayerRebel2::ra2SelectFrameBuffer(int codec, int width, int height) {
_height = surfaceHeight;
}
- if (bufSize > screenSize &&
- _specialBuffer != nullptr && _specialBufferSize >= bufSize) {
+ if (needsSpecialBuffer &&
+ _specialBuffer != nullptr && _specialBufferSize >= bufSize) {
_dst = _specialBuffer;
+ if (_ra2NativeFrameNeedsClear) {
+ ra2ClearCurrentTarget();
+ _ra2NativeFrameNeedsClear = false;
+ }
debugC(DEBUG_SMUSH, "SmushPlayerRebel2::ra2SelectFrameBuffer: Using _specialBuffer %dx%d for oversized FOBJ %dx%d",
_width, _height, width, height);
} else {
@@ -962,6 +1151,10 @@ bool SmushPlayerRebel2::handleGameFrameBufferSelect(int codec, int width, int he
bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int height) {
if ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)) {
+ if (_dst == _ra2LowResVideoBuffer && _ra2LowResVideoBuffer != nullptr) {
+ return true;
+ }
+
if (_insane != nullptr) {
InsaneRebel2 *rebel2 = static_cast<InsaneRebel2 *>(_insane);
if (rebel2->getHandler() != 0) {
@@ -983,6 +1176,13 @@ bool SmushPlayerRebel2::handleGameDimensionOverride(int codec, int width, int he
return false;
}
+int SmushPlayerRebel2::handleGameFrameObjectPitch(int pitch) {
+ if (_dst == _ra2LowResVideoBuffer && _ra2LowResVideoBuffer != nullptr && _width > 0)
+ return _width;
+
+ return pitch;
+}
+
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;
@@ -997,7 +1197,27 @@ bool SmushPlayerRebel2::handleGameAdjustCoords(int codec, int &left, int &top, i
return true;
}
- adjustFrameCoords(left, top, width, height, pitch, &sourceSkipY);
+ if (_dst == _ra2LowResVideoBuffer && _ra2LowResVideoBuffer != nullptr) {
+ left += _fobjOffsetX;
+ top += _fobjOffsetY;
+
+ if (top < 0) {
+ sourceSkipY = -top;
+ height += top;
+ top = 0;
+ }
+ if (left < 0) {
+ width += left;
+ left = 0;
+ }
+ if (top + height > _height)
+ height = _height - top;
+ if (left + width > pitch)
+ width = pitch - left;
+ } else {
+ 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_UNCOMPRESSED) {
_ra2FrameSourceSkipY = sourceSkipY;
@@ -1042,29 +1262,43 @@ void SmushPlayerRebel2::handleGameFrameObjectPost(int codec, const byte *data, i
}
}
+void SmushPlayerRebel2::handleGameFrameObjectDecoded(int codec, int left, int top, int width, int height) {
+ if (!ra2IsHighResMode() || isRebel2GameplayActive(_insane))
+ return;
+
+ ra2PromoteCurrentFrameToHiRes(0, 0);
+}
+
void SmushPlayerRebel2::handleGameFrameStart() {
_hasFrameFobjForGost = false;
+ _ra2NativeFrameNeedsClear = ((_curVideoFlags & 0x20) == 0);
if (_ra2PendingAnimHeaderPalette) {
setDirtyColors(0, 255);
_ra2PendingAnimHeaderPalette = false;
}
+ if (ra2IsHighResMode() && isRebel2GameplayActive(_insane)) {
+ if (_ra2UsingGameplaySurface && _specialBuffer != nullptr &&
+ _specialBufferSize >= kRebel2GameplaySurfaceWidth * kRebel2GameplaySurfaceHeight) {
+ _dst = _specialBuffer;
+ _width = kRebel2GameplaySurfaceWidth;
+ _height = kRebel2GameplaySurfaceHeight;
+ } else if (ra2EnsureLowResVideoBuffer()) {
+ _dst = _ra2LowResVideoBuffer;
+ _width = 320;
+ _height = 200;
+ }
+ }
+
// FUN_00424d70 clears the target buffer before decoding a frame
// unless playback flags contain 0x20. Codec 21 frames in levels like
// LEV05/05PLAY.SAN only contain non-zero literals, so stale skipped pixels
// must not survive from the previous frame.
if ((_curVideoFlags & 0x20) == 0 && _dst != nullptr) {
- int clearSize = 0;
- if (_dst == _specialBuffer && _width > 0 && _height > 0) {
- const int64 size64 = (int64)_width * _height;
- if (size64 <= INT_MAX && size64 <= _specialBufferSize)
- clearSize = (int)size64;
- } else {
- clearSize = _vm->_screenWidth * _vm->_screenHeight;
- }
- if (clearSize > 0)
- memset(_dst, 0, clearSize);
+ ra2ClearCurrentTarget();
+ if (!ra2IsHighResMode() || isRebel2GameplayActive(_insane) || _dst != _vm->_virtscr[kMainVirtScreen].getPixels(0, 0))
+ _ra2NativeFrameNeedsClear = false;
}
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra2.h b/engines/scumm/smush/rebel/smush_player_ra2.h
index 81cb8ea62dd..86035fc6bc7 100644
--- a/engines/scumm/smush/rebel/smush_player_ra2.h
+++ b/engines/scumm/smush/rebel/smush_player_ra2.h
@@ -30,6 +30,7 @@ class SmushPlayerRebel2 : public SmushPlayer {
public:
SmushPlayerRebel2(ScummEngine_v7 *scumm, IMuseDigital *imuseDigital, Insane *insane);
~SmushPlayerRebel2() override;
+ bool ra2PromoteCurrentFrameToHiRes(int scrollX, int scrollY);
protected:
void initGamePlayerFields() override;
@@ -50,11 +51,13 @@ protected:
bool shouldRouteAllIACTs() const override { return true; }
bool handleGameFrameBufferSelect(int codec, int width, int height) override;
bool handleGameDimensionOverride(int codec, int width, int height) override;
+ int handleGameFrameObjectPitch(int pitch) override;
bool handleGameAdjustCoords(int codec, int &left, int &top, int &width, int &height, int pitch, int *srcSkipY) override;
bool handleGameCodecDecode(int codec, const uint8 *src, int left, int top, int width, int height, int pitch, int dataSize, uint8 param = 0, uint16 parm2 = 0) override;
bool handleGameStoreFrame() override;
void handleGameFrameObjectPre(int codec, int left, int top, int width, int height, int dataSize) override;
void handleGameFrameObjectPost(int codec, const byte *data, int32 dataSize, int left, int top, int width, int height) override;
+ void handleGameFrameObjectDecoded(int codec, int left, int top, int width, int height) override;
void handleGameFrameStart() override;
bool handleGameSkipChunk(uint32 subType, int32 subSize, Common::SeekableReadStream &b) override;
void handleGameGost(int32 subSize, Common::SeekableReadStream &b) override;
@@ -69,6 +72,9 @@ private:
int width, int height, TextStyleFlags flg);
void ra2PrepareFrameObjectSurface(int left, int top, int width, int height);
bool ra2SelectFrameBuffer(int codec, int width, int height);
+ bool ra2EnsureLowResVideoBuffer();
+ void ra2ClearCurrentTarget();
+ bool ra2IsHighResMode() const;
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);
@@ -89,6 +95,10 @@ private:
int _ra2FrameObjectOriginalWidth;
int _ra2FrameObjectSurfaceWidth;
int _ra2FrameObjectSurfaceHeight;
+ byte *_ra2LowResVideoBuffer;
+ int _ra2LowResVideoBufferSize;
+ bool _ra2NativeFrameNeedsClear;
+ bool _ra2UsingGameplaySurface;
bool _ra2PendingAnimHeaderPalette;
byte _ra2Codec45Palette[0x300];
byte _ra2Codec45Lookup[0x8000];
diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp
index a6db75dfc8e..067fe39a575 100644
--- a/engines/scumm/smush/smush_player.cpp
+++ b/engines/scumm/smush/smush_player.cpp
@@ -894,6 +894,7 @@ void SmushPlayer::decodeFrameObject(int codec, const uint8 *src, int left, int t
int pitch = _vm->_screenWidth;
if (_dst == _specialBuffer)
pitch = _width;
+ pitch = handleGameFrameObjectPitch(pitch);
// Save original FOBJ position and dimensions before clipping. Codec 37/47
// (delta block/glyph) decode the full frame starting at (0,0). Codec 2
@@ -949,6 +950,8 @@ void SmushPlayer::decodeFrameObject(int codec, const uint8 *src, int left, int t
}
}
+ handleGameFrameObjectDecoded(codec, left, top, width, height);
+
if (_storeFrame && !handleGameStoreFrame()) {
if (_frameBuffer == nullptr) {
_frameBuffer = (byte *)malloc(_width * _height);
diff --git a/engines/scumm/smush/smush_player.h b/engines/scumm/smush/smush_player.h
index a5dc1ff6f79..fe55b5e7ec1 100644
--- a/engines/scumm/smush/smush_player.h
+++ b/engines/scumm/smush/smush_player.h
@@ -318,11 +318,13 @@ protected:
virtual bool shouldRouteAllIACTs() const { return false; }
virtual bool handleGameFrameBufferSelect(int codec, int width, int height) { return false; }
virtual bool handleGameDimensionOverride(int codec, int width, int height) { return false; }
+ virtual int handleGameFrameObjectPitch(int pitch) { return pitch; }
virtual bool handleGameAdjustCoords(int codec, int &left, int &top, int &width, int &height, int pitch, int *srcSkipY) { return false; }
virtual bool handleGameCodecDecode(int codec, const uint8 *src, int left, int top, int width, int height, int pitch, int dataSize, uint8 param = 0, uint16 parm2 = 0) { return false; }
virtual bool handleGameStoreFrame() { return false; }
virtual void handleGameFrameObjectPre(int codec, int left, int top, int width, int height, int dataSize) {}
virtual void handleGameFrameObjectPost(int codec, const byte *data, int32 dataSize, int left, int top, int width, int height) {}
+ virtual void handleGameFrameObjectDecoded(int codec, int left, int top, int width, int height) {}
virtual void handleGameFrameStart() {}
virtual bool handleGameSkipChunk(uint32 subType, int32 subSize, Common::SeekableReadStream &b) { return false; }
virtual void handleGameGost(int32 subSize, Common::SeekableReadStream &b) {}
Commit: b05208d42899f28bbc523ad0727bb854d6064666
https://github.com/scummvm/scummvm/commit/b05208d42899f28bbc523ad0727bb854d6064666
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: perspective correction in high resolution mode for L2
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/insane/rebel2/runlevels.cpp
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 6487dd5528b..607838a25fd 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -1769,8 +1769,9 @@ bool InsaneRebel2::handleOpcode8EmbeddedAnim(byte *renderBitmap, byte *animData,
// par4=5: 320x200 background -> DAT_0048226c
// par4=6: Overlay -> DAT_00482250, draws immediately
// par4=7: Overlay -> DAT_00482248, draws immediately
+ // par4=10: GRD005 - Mode 3 overlay -> DAT_00482258 / _grd005Sprite
if (!handled && _rebelHandler == 25) {
- if (par4 == 1 || par4 == 2) {
+ if (par4 == 1 || par4 == 2 || par4 == 10) {
handled = loadHandler25GrdSprites(animData, animDataSize, par4);
} else if (par4 == 5) {
handled = loadLevel2Background(animData, animDataSize, renderBitmap);
@@ -2242,13 +2243,14 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
// par4 values (from IACT data offset +6):
// 1: GRD001 - Primary ship sprite (DAT_00482240 / _grd001Sprite)
// 2: GRD002 - Secondary ship sprite (DAT_00482238 / _grd002Sprite)
+ // 10: GRD005 - Mode 3 overlay sprite (DAT_00482258 / _grd005Sprite)
if (!animData || size <= 0) {
return false;
}
// Only handle valid GRD sprite slots
- if (par4 != 1 && par4 != 2) {
+ if (par4 != 1 && par4 != 2 && par4 != 10) {
return false;
}
@@ -2273,6 +2275,11 @@ bool InsaneRebel2::loadHandler25GrdSprites(byte *animData, int32 size, int16 par
_grd002Sprite = newNut;
debug("Rebel2: _grd002Sprite set with %d sprites", newNut->getNumChars());
break;
+ case 10: // GRD005 - Mode 3 overlay sprite
+ delete _grd005Sprite;
+ _grd005Sprite = newNut;
+ debug("Rebel2: _grd005Sprite set with %d sprites", newNut->getNumChars());
+ break;
default:
delete newNut;
return false;
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 93c786740df..07a9f1842ed 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -440,6 +440,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Initialize Handler 25 GRD ship system
_grd001Sprite = nullptr; // DAT_00482240 - GRD001 primary ship
_grd002Sprite = nullptr; // DAT_00482238 - GRD002 secondary ship
+ _grd005Sprite = nullptr; // DAT_00482258 - GRD005 mode 3 overlay
_grdSpriteMode = 0; // DAT_00457900 - sprite mode (1,2,3,4)
memset(_grdShotOriginX, 0, sizeof(_grdShotOriginX));
memset(_grdShotOriginY, 0, sizeof(_grdShotOriginY));
@@ -604,6 +605,7 @@ InsaneRebel2::~InsaneRebel2() {
// Clean up Handler 25 GRD ship sprites
delete _grd001Sprite;
delete _grd002Sprite;
+ delete _grd005Sprite;
// Clean up Handler 0x26 turret HUD overlays
delete _hudOverlayNut;
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index f7a5490dbf8..2a7e42c8854 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -1191,6 +1191,7 @@ public:
// Based on FUN_0041cadb case 6 and FUN_0041db5e disassembly:
// - DAT_00482240: Primary ship sprite (GRD001, par4=1)
// - DAT_00482238: Secondary ship sprite (GRD002, par4=2)
+ // - DAT_00482258: Mode 3 overlay sprite (GRD005, par4=10)
// - DAT_00457900: Sprite mode (1,2,3,4) controls which sprite to draw
// - DAT_00457910: Ship X screen position
// - DAT_00457912: Ship Y screen position
@@ -1199,6 +1200,7 @@ public:
NutRenderer *_grd001Sprite; // DAT_00482240 - GRD001 primary ship NUT
NutRenderer *_grd002Sprite; // DAT_00482238 - GRD002 secondary ship NUT
+ NutRenderer *_grd005Sprite; // DAT_00482258 - GRD005 mode 3 overlay NUT
// Handler 25 shot-origin lookup tables from opcode 8/par4=8 text payload.
// Indices 5..19 are filled by the retail "%hd %hd ..." parser (FUN_0041CADB case 6).
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 50534d256e2..3d6eb84afde 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2346,11 +2346,12 @@ void InsaneRebel2::updatePostRenderScroll(int width, int height) {
return;
}
- if (_rebelHandler == 25 && !isHiRes()) {
- // Handler 25's low-res L2 corridor layers are authored for the 320x200
- // viewport. The backing buffer may be larger to decode unclipped FOBJ
- // data, but panning the final copy exposes unfilled columns/rows and
- // breaks the corridor perspective.
+ if (_rebelHandler == 25) {
+ // Handler 25's L2 corridor layers are authored for the 320x200 viewport.
+ // The backing buffer may be larger to decode unclipped FOBJ data, but
+ // panning the final copy exposes unfilled columns/rows and breaks the
+ // corridor perspective. High-res mode must scale that same locked native
+ // viewport instead of selecting a different region of the 424x260 buffer.
_viewX = 0;
_viewY = 0;
_player->setScrollOffset(0, 0);
@@ -3684,6 +3685,27 @@ void InsaneRebel2::renderHandler25ShipPre(byte *renderBitmap, int pitch, int wid
debug("Rebel2 Handler25 PRE: GRD001 at (%d,%d) nutOff(%d,%d) viewOff(%d,%d) size(%d,%d) mode=%d dmg=%d halfW=%d rightHalf=%d clip=[%d,%d) scale=%d",
drawX, drawY, spriteXOffset, spriteYOffset, _rebelViewOffset2X, _rebelViewOffset2Y,
spriteW, spriteH, _grdSpriteMode, _rebelDamageLevel, useHalfWidth ? 1 : 0, useRightHalf ? 1 : 0, scaledClipLeft, scaledClipRight, renderScale);
+
+ if (_grdSpriteMode == 3 && _grd005Sprite && _grd005Sprite->getNumChars() > 1) {
+ const int overlayIdx = 1;
+ int overlayW = _grd005Sprite->getCharWidth(overlayIdx);
+ int overlayH = _grd005Sprite->getCharHeight(overlayIdx);
+ int16 overlayXOffset = _grd005Sprite->getCharXOffset(overlayIdx);
+ int16 overlayYOffset = _grd005Sprite->getCharYOffset(overlayIdx);
+
+ int nativeOverlayX = _rebelViewOffset2X + overlayXOffset + nativeBufferViewX;
+ int nativeOverlayY = _rebelViewOffset2Y + overlayYOffset + nativeBufferViewY;
+ int overlayDrawX = renderHiRes ? (nativeOverlayX - nativeViewX) * renderScale : nativeOverlayX;
+ int overlayDrawY = renderHiRes ? (nativeOverlayY - nativeViewY) * renderScale : nativeOverlayY;
+
+ renderNutSpriteScaledClipped(renderBitmap, pitch, width, renderHeight,
+ 0, 0, width, renderHeight,
+ overlayDrawX, overlayDrawY, _grd005Sprite, overlayIdx, false, renderScale, false);
+
+ debug("Rebel2 Handler25 PRE: GRD005 at (%d,%d) nutOff(%d,%d) viewOff(%d,%d) size(%d,%d) mode=%d scale=%d",
+ overlayDrawX, overlayDrawY, overlayXOffset, overlayYOffset,
+ _rebelViewOffset2X, _rebelViewOffset2Y, overlayW, overlayH, _grdSpriteMode, renderScale);
+ }
}
}
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 2048677e5ea..e75dadb9d74 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -278,6 +278,8 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
_grd001Sprite = nullptr;
delete _grd002Sprite;
_grd002Sprite = nullptr;
+ delete _grd005Sprite;
+ _grd005Sprite = nullptr;
_grdShotOriginTableLoaded = false;
clearEmbeddedHudFrames();
Commit: f00a3eb8de71ff21e662b9fa82462abc7c5eb3d5
https://github.com/scummvm/scummvm/commit/f00a3eb8de71ff21e662b9fa82462abc7c5eb3d5
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: gameplay corrected for in high resolution mode for L3
Changed paths:
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 2a7e42c8854..dfb972235c8 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -604,6 +604,9 @@ public:
// Draw Handler 7 ship sprite (third-person ship - FLY sprites)
void renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height);
+ void renderHandler7FlySprite(byte *renderBitmap, int pitch, int width, int height,
+ bool renderHiRes, int renderScale, int nativeViewX, int nativeViewY,
+ int nativeX, int nativeY, NutRenderer *nut, int spriteIndex);
// Draw Handler 8 ship sprite (third-person on foot - POV sprites)
void renderHandler8Ship(byte *renderBitmap, int pitch, int width, int height);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 3d6eb84afde..b4789c5cb03 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -3403,6 +3403,21 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
}
}
+// renderHandler7FlySprite -- Draw a native Handler 7 FLY sprite into the current presentation target.
+void InsaneRebel2::renderHandler7FlySprite(byte *renderBitmap, int pitch, int width, int height,
+ bool renderHiRes, int renderScale, int nativeViewX, int nativeViewY,
+ int nativeX, int nativeY, NutRenderer *nut, int spriteIndex) {
+ int dstX = renderHiRes ? (nativeX - nativeViewX) * renderScale : nativeX;
+ int dstY = renderHiRes ? (nativeY - nativeViewY) * renderScale : nativeY;
+
+ if (renderHiRes) {
+ renderNutSpriteScaledClipped(renderBitmap, pitch, width, height,
+ 0, 0, width, height, dstX, dstY, nut, spriteIndex, false, renderScale, true);
+ } else {
+ renderNutSprite(renderBitmap, pitch, width, height, dstX, dstY, nut, spriteIndex);
+ }
+}
+
// renderHandler7Ship -- Handler 7 third-person ship rendering (FUN_0040d836).
void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width, int height) {
// Handler 7 Ship Rendering (Third-Person Ship - FLY sprites)
@@ -3421,7 +3436,20 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
if (spriteIndex >= numSprites)
spriteIndex = numSprites - 1;
+ const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+ const int renderScale = renderHiRes ? 2 : 1;
+ const int nativeViewX = renderHiRes ? _hiResPresentationViewX : 0;
+ const int nativeViewY = renderHiRes ? _hiResPresentationViewY : 0;
+
Common::Point shipDraw = getHandler7ShipDrawPoint();
+ if (renderHiRes) {
+ // Low-res draws into the native source buffer with _viewX/_viewY baked in,
+ // then SmushPlayer copies the scrolled viewport. High-res promotion has
+ // already consumed those offsets, so reconstruct the same native source
+ // position before applying the 2x presentation transform.
+ shipDraw.x += nativeViewX;
+ shipDraw.y += nativeViewY;
+ }
int shipCenterX = shipDraw.x + 0xd4;
int shipCenterY = shipDraw.y + 0x82;
@@ -3436,7 +3464,9 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
int cueIndex = _flyEffectAnimCounter % 10;
if (cueIndex >= 0 && cueIndex < laserChars) {
int cueX = ((shipCenterX - 0x28) - leftDist) - leftDist / 2;
- renderNutSprite(renderBitmap, pitch, width, height, cueX, shipCenterY, _flyLaserSprite, cueIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ cueX, shipCenterY, _flyLaserSprite, cueIndex);
}
}
@@ -3445,44 +3475,57 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
int cueIndex = (_flyEffectAnimCounter % 10) + 10;
if (cueIndex >= 0 && cueIndex < laserChars) {
int cueX = rightDist / 2 + rightDist + shipCenterX + 0x28;
- renderNutSprite(renderBitmap, pitch, width, height, cueX, shipCenterY, _flyLaserSprite, cueIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ cueX, shipCenterY, _flyLaserSprite, cueIndex);
}
}
} else {
int16 bottomDist = _corridorBottomY - _flyShipScreenY;
int bottomX = shipCenterX;
- int bottomY = (_corridorBottomY - 0x82) + _perspectiveY + 100 + _viewY;
+ int bottomY = (_corridorBottomY - 0x82) + _perspectiveY + 100 +
+ (renderHiRes ? nativeViewY : _viewY);
if (bottomDist < 0x19) {
_flyEffectAnimCounter++;
int cueIndex = _flyEffectAnimCounter % 10;
if (cueIndex >= 0 && cueIndex < laserChars)
- renderNutSprite(renderBitmap, pitch, width, height, bottomX, bottomY, _flyLaserSprite, cueIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ bottomX, bottomY, _flyLaserSprite, cueIndex);
}
if (bottomDist < 0x32) {
_flyEffectAnimCounter++;
int cueIndex = _flyEffectAnimCounter % 10;
if (cueIndex >= 0 && cueIndex < laserChars)
- renderNutSprite(renderBitmap, pitch, width, height, bottomX, bottomY, _flyLaserSprite, cueIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ bottomX, bottomY, _flyLaserSprite, cueIndex);
}
int cueIndex = _flyEffectAnimCounter % 10;
if (cueIndex >= 0 && cueIndex < laserChars)
- renderNutSprite(renderBitmap, pitch, width, height, bottomX, bottomY, _flyLaserSprite, cueIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ bottomX, bottomY, _flyLaserSprite, cueIndex);
}
}
- int drawX = shipDraw.x;
- int drawY = shipDraw.y;
+ int drawX = renderHiRes ? (shipDraw.x - nativeViewX) * renderScale : shipDraw.x;
+ int drawY = renderHiRes ? (shipDraw.y - nativeViewY) * renderScale : shipDraw.y;
- renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _flyShipSprite, spriteIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ shipDraw.x, shipDraw.y, _flyShipSprite, spriteIndex);
// FUN_40D836 lines 176-180: optional FLY002 overlay pass at ship position.
if (_flyLaserSprite && _flyOverlayRepeatCount > 0) {
int overlayIndex = spriteIndex + 0x14;
if (overlayIndex >= 0 && overlayIndex < _flyLaserSprite->getNumChars()) {
for (int i = 0; i < _flyOverlayRepeatCount; i++) {
- renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _flyLaserSprite, overlayIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ shipDraw.x, shipDraw.y, _flyLaserSprite, overlayIndex);
}
}
}
@@ -3490,7 +3533,9 @@ void InsaneRebel2::renderHandler7Ship(byte *renderBitmap, int pitch, int width,
// FUN_40D836 lines 181-183: optional FLY003 overlay in high detail mode.
if (_flyTargetSprite && _rebelDetailMode >= 0 &&
spriteIndex >= 0 && spriteIndex < _flyTargetSprite->getNumChars()) {
- renderNutSprite(renderBitmap, pitch, width, height, drawX, drawY, _flyTargetSprite, spriteIndex);
+ renderHandler7FlySprite(renderBitmap, pitch, width, height,
+ renderHiRes, renderScale, nativeViewX, nativeViewY,
+ shipDraw.x, shipDraw.y, _flyTargetSprite, spriteIndex);
}
debug("Rebel2 Handler7Ship: draw=(%d,%d) sprite=%d/%d shipPos=(%d,%d) persp=(%d,%d) smoothVel=%d vertIn=%d fxCtr=%d fxRep=%d",
@@ -4623,6 +4668,9 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
if (_rebelHandler == 8) {
worldMousePos.x += _shipPosX;
worldMousePos.y += _shipPosY;
+ } else if (_rebelHandler == 7 && isHiRes() && width >= 640 && height >= 400) {
+ worldMousePos.x += _hiResPresentationViewX;
+ worldMousePos.y += _hiResPresentationViewY;
} else if (_rebelHandler != 7) {
worldMousePos.x += _viewX;
worldMousePos.y += _viewY;
Commit: 9644e845610eca7909dc9ec711d90e69c6a1bb16
https://github.com/scummvm/scummvm/commit/9644e845610eca7909dc9ec711d90e69c6a1bb16
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: implement damage recovery
Changed paths:
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index dfb972235c8..f4d8536df49 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -590,6 +590,7 @@ public:
void renderGameplayPostFrame(byte *renderBitmap, int pitch, int width, int height,
int videoWidth, int videoHeight, int statusBarY, int32 curFrame);
void updateGameplayDamageEffects(byte *renderBitmap, int pitch, int width, int height);
+ void updateGameplayDamageRecovery(int32 curFrame);
void checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
// Draw NUT-based HUD overlays for Handler 0x26 turret modes
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index b4789c5cb03..54938cee693 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2602,6 +2602,21 @@ void InsaneRebel2::updateGameplayDamageEffects(byte *renderBitmap, int pitch, in
}
}
+// updateGameplayDamageRecovery -- Apply RA2's damage auto-reduction.
+void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
+ // Handler 0x26 (FUN_4089AB), Handler 8 (FUN_401CCF), and Handler 7
+ // (FUN_40D836) decrement DAT_0047a7ec once every 16 frames after
+ // gameplay collision processing. Handler 25's FUN_41DB5E only awards the
+ // timed score tick in the same slot and does not reduce damage.
+ if ((_rebelHandler != 0x26 && _rebelHandler != 8 && _rebelHandler != 7) ||
+ (curFrame & 0xf) != 0 || _playerDamage <= 0) {
+ return;
+ }
+
+ _playerDamage--;
+ _playerShield = 255 - _playerDamage;
+}
+
// checkGameplayPostRenderCollisions -- Run handler-specific collision checks.
void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
// Per-frame collision checking against registered zones.
@@ -2717,6 +2732,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
updateGameplayDamageEffects(renderBitmap, pitch, width, height);
checkGameplayPostRenderCollisions(renderBitmap, pitch, width, height, curFrame);
+ updateGameplayDamageRecovery(curFrame);
// Crosshair/reticle (FUN_004089ab, FUN_0040d836).
renderCrosshair(renderBitmap, pitch, width, height);
Commit: 74401bdb4db847e2f1c7649cd0ab3d3b03153e04
https://github.com/scummvm/scummvm/commit/74401bdb4db847e2f1c7649cd0ab3d3b03153e04
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA2: some features for yoda mode
Changed paths:
engines/scumm/detection.h
engines/scumm/detection_tables.h
engines/scumm/insane/rebel2/iact.cpp
engines/scumm/insane/rebel2/levels.cpp
engines/scumm/insane/rebel2/menu.cpp
engines/scumm/insane/rebel2/rebel.cpp
engines/scumm/insane/rebel2/rebel.h
engines/scumm/insane/rebel2/render.cpp
engines/scumm/insane/rebel2/runlevels.cpp
engines/scumm/metaengine.cpp
diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index 29f49690434..c64121e80a5 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -42,6 +42,7 @@ namespace Scumm {
#define GAMEOPTION_REBEL2_UNLOCK_ALL GUIO_GAMEOPTIONS11
#define GAMEOPTION_REBEL1_UNLOCK_ALL GUIO_GAMEOPTIONS12
#define GAMEOPTION_REBEL2_NO_DAMAGE GUIO_GAMEOPTIONS13
+#define GAMEOPTION_REBEL2_YODA_MODE GUIO_GAMEOPTIONS14
/**
* Descriptor of a specific SCUMM game. Used internally to store
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index 8e14a3c0152..fb90c801359 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -230,8 +230,8 @@ static const GameSettings gameVariantsTable[] = {
{"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO2(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL)},
- {"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO4(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE)},
- {"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO, Common::kPlatformDOS, GUIO4(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE)},
+ {"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
+ {"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"dig", "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO5(GUIO_NOMIDI, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
{"dig", "Demo", 0, GID_DIG, 7, 0, MDT_NONE, GF_DEMO, UNK, GUIO4(GUIO_NOMIDI, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
diff --git a/engines/scumm/insane/rebel2/iact.cpp b/engines/scumm/insane/rebel2/iact.cpp
index 607838a25fd..a96388d0991 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -1142,7 +1142,7 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
// Autopilot logic (lines 123-146).
// From original FUN_0041cadb - NO damageLevel check, toggle happens immediately.
// The damage level counter provides the smooth visual transition.
- if (!_rebelInvulnerable) {
+ if (!_rebelAutoPlay) {
if (_rebelAutopilot == 0) {
// Uncovered: RIGHT button enters cover.
if ((_rebelControlMode & 2) != 0) {
@@ -1160,7 +1160,7 @@ void InsaneRebel2::handleOpcode6Handler25(byte *renderBitmap, Common::SeekableRe
// Clear control mode after processing (sticky flags consumed).
_rebelControlMode = 0;
} else {
- // Invulnerable mode: random autopilot changes.
+ // Auto play: random autopilot changes.
if (_rebelAutopilot == 0) {
if (_vm->_rnd.getRandomNumber(100) == 0) {
_rebelAutopilot = 1;
@@ -1345,7 +1345,7 @@ void InsaneRebel2::handleOpcode6GenericInit(int16 par4) {
void InsaneRebel2::updateOpcode6GenericFlightState() {
// Step 3: Autopilot/control mode logic (lines 123-146)
// This determines whether the ship flies on autopilot or manual control.
- if (!_rebelInvulnerable) {
+ if (!_rebelAutoPlay) {
// Normal mode: check control mode flags.
if (_rebelAutopilot == 0) {
if ((_rebelControlMode & 2) != 0) {
@@ -1357,7 +1357,7 @@ void InsaneRebel2::updateOpcode6GenericFlightState() {
}
}
} else {
- // Invulnerable mode: random autopilot changes.
+ // Auto play: random autopilot changes.
if (_rebelAutopilot == 0) {
if (_vm->_rnd.getRandomNumber(100) == 0) {
_rebelAutopilot = 1;
diff --git a/engines/scumm/insane/rebel2/levels.cpp b/engines/scumm/insane/rebel2/levels.cpp
index b133a232415..ebbdff25fd0 100644
--- a/engines/scumm/insane/rebel2/levels.cpp
+++ b/engines/scumm/insane/rebel2/levels.cpp
@@ -81,10 +81,17 @@ void InsaneRebel2::runGame() {
while (!_vm->shouldQuit()) {
int menuResult = runMainMenu();
- if (menuResult == 0 || _vm->shouldQuit())
+ if (menuResult == kMenuQuit || _vm->shouldQuit())
break;
- if (menuResult == kMenuNewGame || menuResult == kMenuContinue) {
+ if (menuResult == kMenuResumeDemo) {
+ playIntroSequence();
+ if (!_vm->shouldQuit())
+ showTopPilots();
+ continue;
+ }
+
+ if (menuResult == kMenuNewGame) {
int pilotResult = runLevelSelect();
if (pilotResult == kLevelSelectQuit || _vm->shouldQuit())
diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 400928d1832..b80ece9239a 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -48,6 +48,7 @@ namespace Scumm {
void InsaneRebel2::resetMenu() {
_menuSelection = 0;
_menuInactivityTimer = 0;
+ _menuInactivityTimedOut = false;
_menuRepeatDelay = 0;
_menuSelectionConfirmed = false;
setVirtualKeyboardVisible(false);
@@ -591,7 +592,7 @@ void InsaneRebel2::showPauseOverlay() {
}
// runMainMenu -- Main menu loop (FUN_004147B2).
-// Returns kMenuNewGame, kMenuContinue, kMenuCredits, or 0 (quit).
+// Returns kMenuNewGame, kMenuResumeDemo, or kMenuQuit.
int InsaneRebel2::runMainMenu() {
debug("Rebel2: Entering main menu");
@@ -630,7 +631,14 @@ int InsaneRebel2::runMainMenu() {
// Check for quit
if (_vm->shouldQuit()) {
_menuInputActive = false;
- return 0;
+ return kMenuQuit;
+ }
+
+ if (_menuInactivityTimedOut) {
+ debug("Rebel2: Main menu inactivity - resuming intro/demo loop");
+ _menuInactivityTimedOut = false;
+ _menuInputActive = false;
+ return kMenuResumeDemo;
}
// Only process selection if user explicitly confirmed (ENTER/ESC),
@@ -651,7 +659,7 @@ int InsaneRebel2::runMainMenu() {
// case 0 (TRS 11): Start Game -> pilot selection, returns 2
// case 1 (TRS 12): Options -> FUN_00416787 options screen
// case 2 (TRS 13): Calibrate Joystick -> FUN_00425820
- // case 3 (TRS 14): Continue Intro -> replay O_OPEN videos
+ // case 3 (TRS 14): Continue Intro -> return to intro/demo loop
// case 4 (TRS 15): Show Top Pilots -> FUN_00420116(-1)
// case 5 (TRS 16): Show Credits -> play O_CREDIT.SAN, returns 1
// case 6 (TRS 17): Return to Launcher -> quit, returns 0
@@ -673,22 +681,10 @@ int InsaneRebel2::runMainMenu() {
// joystick calibration flow is required here.
break;
- case 3: // Continue Intro -> replay intro videos
- debug("Rebel2: Continue Intro selected - replaying intro");
- // Temporarily switch to intro state to disable menu overlay
- // This emulates FUN_004142BD case 0 behavior
- _gameState = kStateIntro;
+ case 3: // Continue Intro -> return to intro/demo loop
+ debug("Rebel2: Continue Intro selected - resuming intro/demo loop");
_menuInputActive = false;
- // Play intro sequence again (O_OPEN_A/B)
- splayer->setCurVideoFlags(0x20);
- splayer->play("OPEN/O_OPEN_A.SAN", 15);
- if (!_vm->shouldQuit()) {
- splayer->play("OPEN/O_OPEN_B.SAN", 15);
- }
- // Restore menu state
- _gameState = kStateMainMenu;
- _menuInputActive = true;
- break;
+ return kMenuResumeDemo;
case 4: // Show Top Pilots -> high score display (FUN_00420116(-1))
debug("Rebel2: Show Top Pilots selected");
@@ -709,7 +705,7 @@ int InsaneRebel2::runMainMenu() {
case 6: // Return to Launcher -> quit game
debug("Rebel2: Return to Launcher selected");
_menuInputActive = false;
- return 0; // Return 0 to exit
+ return kMenuQuit;
default:
debug("Rebel2: Unknown menu selection %d", _menuSelection);
@@ -718,7 +714,7 @@ int InsaneRebel2::runMainMenu() {
}
_menuInputActive = false;
- return 0;
+ return kMenuQuit;
}
// ---------------------------------------------------------------------------
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 07a9f1842ed..9959ca44bdd 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -208,11 +208,13 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_textOverlayFadeIn = 0;
_textOverlayFadeOut = 0;
- // Retail globals mapped: hit counter, cooldown, invulnerability flag
+ // Retail globals mapped: hit counter, cooldown, movie/auto-play flags
_rebelOp6Initialized = false;
_rebelHitCounter = 0;
_rebelKillCounter = 0;
- _rebelInvulnerable = false;
+ _rebelYodaMode = false;
+ _rebelMovieMode = false;
+ _rebelAutoPlay = false;
_rebelWaveState = 0;
_rebelPhaseState = 0;
@@ -476,6 +478,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// FUN_0041f5ae uses the selectable item count for Y position calculation.
_menuItemCount = 7;
_menuInactivityTimer = 0;
+ _menuInactivityTimedOut = false;
_lastMenuVariant = -1; // No previous menu video
_menuRepeatDelay = 0;
_menuSelectionConfirmed = false;
@@ -495,6 +498,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Set to true to bypass normal unlock progression
_debugUnlockAll = ConfMan.getBool("rebel2_unlock_all");
_noDamage = ConfMan.getBool("rebel2_no_damage");
+ _rebelYodaMode = ConfMan.getBool("rebel2_yoda_mode");
for (i = 0; i < 16; i++) {
// If debug unlock is enabled, unlock all chapters
@@ -533,7 +537,7 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
// Initialize options menu state (FUN_004167A6 defaults)
_optionsSelection = 0;
- _optionsItemCount = 9;
+ _optionsItemCount = 8;
_optMusicEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType);
_optSfxEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kSFXSoundType);
_optVoicesEnabled = !_vm->_mixer->isSoundTypeMuted(Audio::Mixer::kSpeechSoundType);
@@ -678,6 +682,29 @@ bool InsaneRebel2::notifyEvent(const Common::Event &event) {
if (_vm->isPaused())
return false;
+ if (_rebelYodaMode && event.type == Common::EVENT_KEYDOWN && !event.kbdRepeat && event.kbd.hasFlags(Common::KBD_ALT)) {
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_m:
+ // Retail DAT_0047ab60: Yoda-mode Movie Mode skips playable
+ // sections and keeps the story/cutscene sequence moving.
+ _rebelMovieMode = !_rebelMovieMode;
+ debug("Rebel2: Movie mode %s", _rebelMovieMode ? "enabled" : "disabled");
+ if (_rebelMovieMode && splayer && _gameState == kStateGameplay && _rebelHandler != 0)
+ _vm->_smushVideoShouldFinish = true;
+ return true;
+
+ case Common::KEYCODE_p:
+ // Retail DAT_0047ab64: Yoda-mode Auto Play makes gameplay
+ // computer controlled.
+ _rebelAutoPlay = !_rebelAutoPlay;
+ debug("Rebel2: Auto play %s", _rebelAutoPlay ? "enabled" : "disabled");
+ return true;
+
+ default:
+ break;
+ }
+ }
+
if (_gameState == kStateGameplay && _rebelHandler == 7 &&
event.type == Common::EVENT_MOUSEMOVE) {
if (_gameplayMouseSettleUntil != 0) {
@@ -1325,7 +1352,7 @@ InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
}
bool InsaneRebel2::applyPlayerDamage(int damage) {
- if (_noDamage || _rebelInvulnerable || damage <= 0)
+ if (_noDamage || _rebelAutoPlay || damage <= 0)
return false;
_playerDamage += damage;
@@ -1615,8 +1642,16 @@ int32 InsaneRebel2::processMouse() {
// Shot trigger behavior:
// - 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;
bool canShoot = isShootingAllowed();
+ bool autoFire = _rebelAutoPlay && canShoot && _gameState == kStateGameplay && _rebelHandler != 0;
+ if (autoFire && _player) {
+ const int autoFirePeriod = (_rebelHandler == 8) ? 6 : 7;
+ autoFire = (_player->_frame % autoFirePeriod) == 0;
+ }
+ if (autoFire)
+ _rebelControlMode |= 1;
+
+ bool triggerShot = ((_rebelHandler == 25) ? (leftPressed && !leftWasPressed) : leftPressed) || autoFire;
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
@@ -1625,7 +1660,9 @@ int32 InsaneRebel2::processMouse() {
}
if (triggerShot && canShoot) {
Common::Point mousePos;
- if (_rebelHandler == 7) {
+ if (autoFire) {
+ mousePos = getRebelAutoPlayAimPoint();
+ } else if (_rebelHandler == 7) {
mousePos = getHandler7ShotTargetPoint();
} else if (_rebelHandler == 8) {
mousePos = getHandler8ShotTargetPoint();
@@ -1776,10 +1813,47 @@ int32 InsaneRebel2::processMouse() {
return buttons;
}
+Common::Point InsaneRebel2::getRebelAutoPlayAimPoint() {
+ Common::Point target(160, 100);
+ int bestDistance = 0x7fffffff;
+
+ for (Common::List<enemy>::iterator it = _enemies.begin(); it != _enemies.end(); ++it) {
+ if (!it->active || it->destroyed)
+ continue;
+
+ int x = (it->rect.left + it->rect.right) / 2;
+ int y = (it->rect.top + it->rect.bottom) / 2;
+ if (_rebelHandler == 8) {
+ x -= _shipPosX;
+ y -= _shipPosY;
+ } else if (_rebelHandler != 7) {
+ x -= _viewX;
+ y -= _viewY;
+ }
+
+ if (x < -32 || x > 351 || y < -32 || y > 231)
+ continue;
+
+ const int dx = x - 160;
+ const int dy = y - 100;
+ const int distance = dx * dx + dy * dy;
+ if (distance < bestDistance) {
+ bestDistance = distance;
+ target.x = CLIP<int>(x, 0, 319);
+ target.y = CLIP<int>(y, 0, 199);
+ }
+ }
+
+ return target;
+}
+
Common::Point InsaneRebel2::getGameplayAimPoint() {
// Pure getter (queried many times per frame): the aim/reticle follows the virtual
// mouse position. Directional controls pan that position incrementally once per frame
// via updateGameplayAimFromGamepad(), rather than snapping the reticle to a screen edge.
+ if (_rebelAutoPlay && _gameState == kStateGameplay && !_menuInputActive)
+ return getRebelAutoPlayAimPoint();
+
int x = _vm->_mouse.x;
int y = _vm->_mouse.y;
if (isHiRes()) {
@@ -1803,7 +1877,7 @@ int16 applyRebel2AnalogDeadzone(int16 axisValue) {
}
void InsaneRebel2::updateGameplayAimFromGamepad() {
- if (_menuInputActive || _gameState != kStateGameplay)
+ if (_menuInputActive || _gameState != kStateGameplay || _rebelAutoPlay)
return;
const int dpadX = (_vm->getActionState(kScummActionInsaneRight) ? 1 : 0) -
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index f4d8536df49..1b263aed8ee 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -79,19 +79,17 @@ public:
// Menu selection results (return values from FUN_004147B2)
enum MenuResult {
- kMenuNewGame = 2, // case 0: New Game
- kMenuContinue = 4, // case 1: Continue
- kMenuOptions = 0, // case 2: Options (stays in menu)
- kMenuExit = 0, // case 3: Exit to title
- kMenuUnknown = 0, // case 4: Unknown function
+ kMenuQuit = -1, // case 6: Return to Launcher
+ kMenuResumeDemo = 0, // case 3 / inactivity: Continue Intro
kMenuCredits = 1, // case 5: Show credits
- kMenuQuit = 0 // case 6: Quit game
+ kMenuNewGame = 2 // case 0: Start Game
};
GameState _gameState; // Current game state
int _menuSelection; // Current menu item (0-6), mirrors DAT_00459988
int _menuItemCount; // Number of menu items (7 for main menu)
int _menuInactivityTimer; // Timeout counter (300 frames = ~10 sec)
+ bool _menuInactivityTimedOut; // Main menu should return to the intro/demo loop
int _lastMenuVariant; // Last random menu video shown (DAT_00482400)
int _menuRepeatDelay; // Delay for key repeat (DAT_00459ce0)
bool _menuSelectionConfirmed; // True only when user explicitly confirmed a selection
@@ -219,8 +217,8 @@ public:
// ---------------------------------------------------------------------------
// TRS strings: 89 (title), 90-101 (toggle labels), 103 (volume), 107/109 (back)
// Original settings array at DAT_00482e20[0..4]:
- // [0]=auto control, [1]=music, [2]=voices, [3]=sound
- // Additional flags: DAT_0047a7fe (text/indicators), DAT_0047a80a (rapid fire/arrows)
+ // [0]=text, [1]=music, [2]=voices, [3]=sound, [4]=hidden abort flag
+ // Additional flags: DAT_0047a7fe (controls normal/flipped), DAT_0047a80a (rapid fire)
// Volume: DAT_0047a804 (0-127), SFX vol: DAT_0047a802 (127-768)
void showOptionsMenu();
@@ -511,6 +509,7 @@ public:
int32 processMouse() override;
Common::Point getGameplayAimPoint();
+ Common::Point getRebelAutoPlayAimPoint();
// Per-frame: pan the gameplay reticle incrementally from the held directional controls
// (on-screen/physical gamepad dpad, keyboard arrows) instead of snapping it to a screen
// edge. Call once per frame; getGameplayAimPoint() stays a pure getter.
@@ -929,7 +928,9 @@ public:
bool _rebelOp6Initialized; // Guard: opcode 6 init block (clearBit/links/wave) runs once per video
int _rebelHitCounter; // DAT_0047ab80 - hit counter / state tracker
int _rebelKillCounter; // DAT_0047ab88 - enemies destroyed this phase
- bool _rebelInvulnerable; // DAT_0047ab64 - toggles invulnerability / state
+ bool _rebelYodaMode; // DAT_0047ab5a > 2 - unlocks Yoda-mode shortcuts
+ bool _rebelMovieMode; // DAT_0047ab60 - Alt+M skips playable sections
+ bool _rebelAutoPlay; // DAT_0047ab64 - Alt+P computer-controlled play
// Enemy wave/phase state tracking (FUN_004028c5 / FUN_00417b61)
// DAT_0047ab98: Per-wave enemy kill state. Bits set when enemy types are destroyed.
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 54938cee693..408e4d87411 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -1951,7 +1951,7 @@ void InsaneRebel2::checkHandler7ObstacleZones(uint16 &warningMask) {
}
bool InsaneRebel2::applyHandler7WallDamage(int wallDamage) {
- if (_hitCooldown < 5 && !_rebelInvulnerable) {
+ if (_hitCooldown < 5 && !_rebelAutoPlay) {
const bool damageApplied = applyPlayerDamage(wallDamage);
_rebelHitCounter++;
_hitCooldown = 10;
@@ -2515,13 +2515,11 @@ bool InsaneRebel2::handlePostRenderMenuModes(byte *renderBitmap, int pitch, int
// At 12fps video rate, 300 frames = ~25 seconds of inactivity.
// The original checks: if (local_8 > 299) return 0.
if (_menuInactivityTimer > 300) {
- debug("Rebel2: Menu inactivity timeout - ending video to loop");
- // Signal video to end so menu loop plays new video.
- // This emulates the attract mode behavior where a new random
- // menu video is selected after inactivity.
+ debug("Rebel2: Menu inactivity timeout - resuming intro/demo loop");
_menuInactivityTimer = 0;
- // Don't set _smushVideoShouldFinish here - let video end naturally.
- // This will cause runMainMenu to loop and play a new random video.
+ _menuInactivityTimedOut = true;
+ _menuSelectionConfirmed = false;
+ _vm->_smushVideoShouldFinish = true;
}
// Draw menu selection overlay.
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index e75dadb9d74..d09d5bb4b4c 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -100,6 +100,16 @@ InsaneRebel2::WaveEndResult InsaneRebel2::processWaveEnd(int16 mask, int16 *budg
WaveEndResult result;
+ if (_rebelMovieMode) {
+ _skipSectionRequested = false;
+ _rebelPhaseState = mask;
+ _rebelWaveState = mask;
+ debug("Rebel2 processWaveEnd: movie mode completed gameplay wave (mask=0x%x)", (uint16)mask);
+ result.completed = true;
+ result.skipped = true;
+ return result;
+ }
+
// Debug shortcut path: force-end current section when requested via Shift+S.
if (_skipSectionRequested) {
_skipSectionRequested = false;
@@ -192,6 +202,16 @@ bool InsaneRebel2::playLevelSegment(const char *filename, uint16 flags, bool rec
SmushPlayer *splayer = ((ScummEngine_v7 *)_vm)->_splayer;
const bool isRecordedGameplay = recordFrame && (flags & 0x08) != 0;
+ if (isRecordedGameplay && _rebelMovieMode) {
+ debug("Rebel2: Movie mode skipping gameplay segment: %s", filename);
+ _gameplaySectionActive = false;
+ restoreIOSGamepadController();
+ if (recordFrame)
+ _deathFrame = 0;
+ restoreDamageFlashPalette();
+ return true;
+ }
+
if (isRecordedGameplay) {
// Center only at the section boundary; looped wave videos are continuations.
if (!_gameplaySectionActive && (flags & 0x40) == 0)
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 0f75ecbec27..ba6e3e32042 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -905,6 +905,15 @@ static const ExtraGuiOption enableRebel2NoDamage = {
0
};
+static const ExtraGuiOption enableRebel2YodaMode = {
+ _s("Yoda mode"),
+ _s("Enable original Yoda mode shortcuts, including movie mode and auto play"),
+ "rebel2_yoda_mode",
+ false,
+ 0,
+ 0
+};
+
const ExtraGuiOption enableRebel1UnlockAll = {
_s("Unlock all levels"),
_s("All levels will be available without requiring passwords"),
@@ -964,6 +973,9 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL2_NO_DAMAGE)) {
options.push_back(enableRebel2NoDamage);
}
+ if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL2_YODA_MODE)) {
+ options.push_back(enableRebel2YodaMode);
+ }
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_UNLOCK_ALL)) {
options.push_back(enableRebel1UnlockAll);
}
Commit: 50674c1a69af97ec6a98cb5296640baf66ca9153
https://github.com/scummvm/scummvm/commit/50674c1a69af97ec6a98cb5296640baf66ca9153
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-09T15:21:42+02:00
Commit Message:
SCUMM: RA1: no damage mode
Changed paths:
engines/scumm/detection.h
engines/scumm/detection_tables.h
engines/scumm/insane/rebel1/iact.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/metaengine.cpp
diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index c64121e80a5..b04b215f58b 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -43,6 +43,7 @@ namespace Scumm {
#define GAMEOPTION_REBEL1_UNLOCK_ALL GUIO_GAMEOPTIONS12
#define GAMEOPTION_REBEL2_NO_DAMAGE GUIO_GAMEOPTIONS13
#define GAMEOPTION_REBEL2_YODA_MODE GUIO_GAMEOPTIONS14
+#define GAMEOPTION_REBEL1_NO_DAMAGE GUIO_GAMEOPTIONS15
/**
* Descriptor of a specific SCUMM game. Used internally to store
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index fb90c801359..f6baccb46fa 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -228,7 +228,7 @@ static const GameSettings gameVariantsTable[] = {
{"ft", "Remastered", 0, GID_FT, 7, 0, MDT_NONE, GF_DOUBLEFINE_PAK, UNK, GUIO5(GUIO_NOMIDI, GAMEOPTION_ENHANCEMENTS, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
{"ft", "Demo", 0, GID_FT, 7, 0, MDT_NONE, GF_DEMO, UNK, GUIO4(GUIO_NOMIDI, GAMEOPTION_ORIGINALGUI, GAMEOPTION_LOWLATENCYAUDIO, GAMEOPTION_TTS)},
- {"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO2(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL)},
+ {"rebel1", "", 0, GID_REBEL1, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO3(GUIO_NOMIDI, GAMEOPTION_REBEL1_UNLOCK_ALL, GAMEOPTION_REBEL1_NO_DAMAGE)},
{"rebel2", "", 0, GID_REBEL2, 7, 0, MDT_NONE, 0, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
{"rebel2", "Demo", 0, GID_REBEL2, 7, 0, MDT_NONE, GF_DEMO, Common::kPlatformDOS, GUIO5(GUIO_NOMIDI, GAMEOPTION_REBEL2_HIRES, GAMEOPTION_REBEL2_UNLOCK_ALL, GAMEOPTION_REBEL2_NO_DAMAGE, GAMEOPTION_REBEL2_YODA_MODE)},
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 9c96d37650a..59c2e7b5861 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -1223,7 +1223,7 @@ void InsaneRebel1::updateShipPhysics() {
// Damage guard/mask from FUN_1DEB5: (_damageFlags & 0x96) != 0
// damageFlags & 0x96 = bits 1,2,4,7 = wall collisions (0x16) + projectile hit (0x80)
- if ((_damageFlags & 0x96) != 0 && _damageCooldown == 0 &&
+ if (!_noDamage && (_damageFlags & 0x96) != 0 && _damageCooldown == 0 &&
_health >= 0 && _deathTimer <= 0) {
// Projectile hit (bit 7 = 0x80)
if (_damageFlags & 0x80)
@@ -1392,7 +1392,7 @@ void InsaneRebel1::updateTurretPhysics() {
_turretFrameShipCenterValid = true;
// Damage gate from FUN_1E6A7.
- if (_damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer <= 0) {
+ if (!_noDamage && _damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer <= 0) {
if (_damageFlags == 0x80)
_health -= _tuning.shot;
else
@@ -1603,7 +1603,7 @@ void InsaneRebel1::updateGameOp0BPhysics() {
// Damage application (FUN_1CDA7 lines 20-41)
// Original 0x0B mapping: 0x80 -> +0x13, 0x40 -> +0x0F, 0x20 -> +0x11.
// No cooldown â all three damage types can stack each frame
- if (_damageFlags != 0 && _health >= 0 && _deathTimer < 1) {
+ if (!_noDamage && _damageFlags != 0 && _health >= 0 && _deathTimer < 1) {
const int16 oldHealth = _health;
const byte appliedDamageFlags = _damageFlags;
_screenFlash = 5;
@@ -2035,7 +2035,7 @@ void InsaneRebel1::updateOnFootSequence() {
// --- Damage handling (from HandleGameOp19_OnFootSequence) ---
// On-foot damage uses the same heavy-damage tuning byte as ship shot/collision
// damage in the original, not the miss penalty.
- if (_damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer < 1) {
+ if (!_noDamage && _damageFlags != 0 && _damageCooldown == 0 && _health >= 0 && _deathTimer < 1) {
const int16 oldHealth = _health;
_health -= _tuning.shot;
if (_health < 0) {
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 4e981793ec0..e4bfb41ef65 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -307,6 +307,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_hudDirtyFlag = 0;
_maxChapterUnlocked = 0;
_unlockAllLevels = ConfMan.getBool("rebel1_unlock_all");
+ _noDamage = ConfMan.getBool("rebel1_no_damage");
_interactiveVideoActive = false;
_preserveInteractiveRuntimeState = false;
_interactiveVideoCheatSkipped = false;
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index a110ab1e295..71138104d32 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -502,6 +502,7 @@ private:
byte _hudDirtyFlag; // 0x7601: 0xFF after HUD redraw (set by renderHUD)
int16 _maxChapterUnlocked; // 0x7730: highest unlocked passcode slot (0=none)
bool _unlockAllLevels; // ScummVM option: expose level select without passcodes
+ bool _noDamage; // ScummVM option: suppress player damage
static const int16 kMaxHealth = 98;
static const int16 kDeathTimerInit = 30;
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index ba6e3e32042..06c6bd74354 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -923,6 +923,15 @@ const ExtraGuiOption enableRebel1UnlockAll = {
0
};
+const ExtraGuiOption enableRebel1NoDamage = {
+ _s("No damage"),
+ _s("Disable player damage"),
+ "rebel1_no_damage",
+ false,
+ 0,
+ 0
+};
+
const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &target) const {
ExtraGuiOptions options;
// Query the GUI options
@@ -979,6 +988,9 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_UNLOCK_ALL)) {
options.push_back(enableRebel1UnlockAll);
}
+ if (target.empty() || guiOptions.contains(GAMEOPTION_REBEL1_NO_DAMAGE)) {
+ options.push_back(enableRebel1NoDamage);
+ }
if (target.empty() || gameid == "comi") {
options.push_back(comiObjectLabelsOption);
More information about the Scummvm-git-logs
mailing list