[Scummvm-git-logs] scummvm master -> d9fc895bf5e6e9c43ae00274ae88053d317af2d4
neuromancer
noreply at scummvm.org
Thu Jun 4 15:12:23 UTC 2026
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
31a97081b0 SCUMM: RA1: proper indicator rendering for level 8
6748744720 SCUMM: RA1: proper winning condition for level 8
0d51b3e9c2 SCUMM: RA1: adding more missing wining conditions
b820b7a630 SCUMM: RA1: improve shift + S to skip some sequences that have wining conditions
d9fc895bf5 SCUMM: RA1: added missing overlay in level 5
Commit: 31a97081b053069f9b6437c87f199444617ec61e
https://github.com/scummvm/scummvm/commit/31a97081b053069f9b6437c87f199444617ec61e
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T15:19:15+02:00
Commit Message:
SCUMM: RA1: proper indicator rendering for level 8
Changed paths:
engines/scumm/smush/rebel/smush_player_ra1.cpp
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index aa213b17a65..880eb987be9 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -241,11 +241,13 @@ bool SmushPlayerRebel1::handleGameFetch(int32 subSize, Common::SeekableReadStrea
if (rebel1->isInteractiveVideoActive()) {
const uint16 gameOp = rebel1->getActiveGameOpcode();
const bool fullWidthStoredPatch = (_storedFobjWidth == _vm->_screenWidth);
- // 0x0B (asteroid/surface) and 0x19/0x1A (on-foot) use SetCameraOffset
- // directly â no projection-based FTCH placement. Level 4 phase 2
- // also stores a full-width screen-space patch (320x180) that DOS
- // restores without the centered 1/4 projection warp.
- if (fullWidthStoredPatch || gameOp == 0x0B ||
+ const bool level8WalkerPatch = (gameOp == 0x0B && rebel1->getCurrentLevel() == 7);
+ // Keep the direct viewport placement used by the existing 0x0B
+ // compatibility path, except for Level 8's walker cockpit patch.
+ // DOS FTCH goes through FUN_28D0A, which sets DispatchFobjCodec
+ // flag 0x800; Level 8 depends on that quarter-projection so the
+ // cockpit and RunLevel8Flow indicators move together.
+ if (fullWidthStoredPatch || (gameOp == 0x0B && !level8WalkerPatch) ||
gameOp == 0x19 || gameOp == 0x1A) {
left += _ra1ViewportOffsetX;
top += _ra1ViewportOffsetY;
Commit: 67487447204cab06b657be2eb19eaa42a7d56781
https://github.com/scummvm/scummvm/commit/67487447204cab06b657be2eb19eaa42a7d56781
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T15:30:13+02:00
Commit Message:
SCUMM: RA1: proper winning condition for level 8
Changed paths:
engines/scumm/insane/rebel1/iact.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/iact.cpp b/engines/scumm/insane/rebel1/iact.cpp
index 8baa313f60f..48f1ff485ee 100644
--- a/engines/scumm/insane/rebel1/iact.cpp
+++ b/engines/scumm/insane/rebel1/iact.cpp
@@ -2110,6 +2110,8 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
return;
}
+ const int16 walkerReplayKillCount = (_walkerRoundReplay && _currentLevel == 7) ? _killCount : 0;
+
_damageFlags = 0;
_prevDamageFlags = 0;
_damageCooldown = 0;
@@ -2164,6 +2166,8 @@ void InsaneRebel1::handleGameOpcode5EReset(uint32 param1) {
_gostSlotIdx = 0;
_killCount = 0;
_lastHitTarget = 0;
+ if (_walkerRoundReplay && _currentLevel == 7)
+ _killCount = walkerReplayKillCount;
// Field1 == 0 corresponds to baseline recenter behavior in the original.
if ((int32)param1 == 0) {
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 248b9ae29c5..cdbf2d37e32 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -418,6 +418,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_walkerHealth = 100;
_walkerTimer = 0;
_walkerBranchChoice = 0;
+ _walkerRoundReplay = false;
resetFrameObjectState();
if (loadRA1Nut("SYS/TALKFONT.NUT", _hudFontBank)) {
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 4d1478e66ae..23570cc8d13 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -694,6 +694,7 @@ private:
int16 _walkerHealth; // Walker health percentage (0-100), init=100
int16 _walkerTimer; // Attack window countdown (100â0)
int16 _walkerBranchChoice; // Directional choice: 0=none, 1=left, 2=right
+ bool _walkerRoundReplay; // Preserve walker damage across a replayed L8 route
// Attack window frame numbers per route (3 routes à 3 windows)
// Route 0: 2588/1709/262, Route 1: 2323/1444/-2, Route 2: 877/-2/-2
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 1b7c05a8a0a..35a01e30779 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -821,15 +821,19 @@ bool InsaneRebel1::runLevel8() {
_walkerHealth = 100;
_walkerTimer = 0;
_walkerBranchChoice = 0;
+ _walkerRoundReplay = false;
int route = 0;
int32 routeStartFrame = 0;
+ bool replayingRound = false;
while (!shouldAbortGameFlow()) {
_levelRouteIndex = route;
_pendingRouteIndex = -1;
_pendingRouteStartFrame = routeStartFrame;
_pendingRouteCutoverFrame = -1;
+ _walkerRoundReplay = replayingRound;
playInteractiveVideo(kLevel8Routes[route], routeStartFrame);
+ _walkerRoundReplay = false;
if (shouldAbortGameFlow())
return false;
@@ -839,23 +843,39 @@ bool InsaneRebel1::runLevel8() {
if (_walkerHealth <= 0)
break;
- if (_pendingRouteIndex < 0 || _pendingRouteIndex == route)
- break;
+ if (_pendingRouteIndex >= 0 && _pendingRouteIndex != route) {
+ // RunLevel8Flow uses PlayAnmFile(..., g_frameCounter, 1, -1)
+ // when it branches from one walker route to another. That wrapper
+ // keeps the current ANM alive for seven more gameplay frames, then
+ // opens the destination route while preserving the active state.
+ routeStartFrame = _pendingRouteStartFrame;
+ route = _pendingRouteIndex;
+ replayingRound = false;
+ continue;
+ }
- // RunLevel8Flow uses PlayAnmFile(..., g_frameCounter, 1, -1)
- // when it branches from one walker route to another. That wrapper
- // keeps the current ANM alive for seven more gameplay frames, then
- // opens the destination route while preserving the active state.
- routeStartFrame = _pendingRouteStartFrame;
- route = _pendingRouteIndex;
+ // RunLevel8Flow keeps pumping the active route while the walker
+ // shield register is nonzero. ScummVM's blocking SMUSH play returns
+ // when one route pass ends, so explicitly replay that route and
+ // preserve the accumulated walker damage.
+ debugC(DEBUG_INSANE, "RA1 L8 replaying route=%d walkerHealth=%d killCount=%d",
+ route, (int)_walkerHealth, (int)_killCount);
+ routeStartFrame = 0;
+ _walkerTimer = 0;
+ _walkerBranchChoice = 0;
+ replayingRound = true;
}
_levelRouteIndex = -1;
_pendingRouteIndex = -1;
_pendingRouteStartFrame = 0;
_pendingRouteCutoverFrame = -1;
+ _walkerRoundReplay = false;
- if (_health >= 0) {
+ if (shouldAbortGameFlow())
+ return false;
+
+ if (_health >= 0 && _walkerHealth <= 0) {
playChapterCompleteCinematic("LVL8/L8END.ANM", 8, 0x5F, 5);
return !shouldAbortGameFlow();
}
Commit: 0d51b3e9c2a701daa9c0bb3f22d13c9da0eb1a1b
https://github.com/scummvm/scummvm/commit/0d51b3e9c2a701daa9c0bb3f22d13c9da0eb1a1b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T16:38:32+02:00
Commit Message:
SCUMM: RA1: adding more missing wining conditions
Changed paths:
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index cdbf2d37e32..4cfee698554 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -317,6 +317,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_maxChapterUnlocked = 0;
_unlockAllLevels = ConfMan.getBool("rebel1_unlock_all");
_interactiveVideoActive = false;
+ _preserveInteractiveRuntimeState = false;
_restoreInteractiveVideoAudioState = false;
memset(_savedInteractiveVideoTrackState, 0, sizeof(_savedInteractiveVideoTrackState));
memset(_savedInteractiveVideoTrackGroupId, 0, sizeof(_savedInteractiveVideoTrackGroupId));
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 23570cc8d13..8ff100263bc 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -521,6 +521,7 @@ private:
// True only while an interactive gameplay SMUSH is running.
bool _interactiveVideoActive;
+ bool _preserveInteractiveRuntimeState;
// Path branching for levels with left/right alternative videos.
// Original sets nextSceneA/nextSceneB when GAME 0x07 counter == 394 (0x18A).
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 35a01e30779..7f4e7ce0d41 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -551,13 +551,35 @@ bool InsaneRebel1::runLevel4() {
_shieldGenHitsA = 0;
_shieldGenHitsB = 0;
_levelGameplayPhase = 1;
- playInteractiveVideo("LVL4/L4PLAY1.ANM");
+ bool shieldGeneratorsDestroyed = false;
+ bool replayingShieldGeneratorPhase = false;
+ while (!shouldAbortGameFlow()) {
+ _preserveInteractiveRuntimeState = replayingShieldGeneratorPhase;
+ playInteractiveVideo("LVL4/L4PLAY1.ANM");
+ _preserveInteractiveRuntimeState = false;
+ if (shouldAbortGameFlow())
+ return false;
+
+ if (_health < 0)
+ break;
+
+ shieldGeneratorsDestroyed =
+ _protectedTargetA == 0 && _protectedTargetB == 0 &&
+ _shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30;
+ if (shieldGeneratorsDestroyed)
+ break;
+
+ debugC(DEBUG_INSANE, "RA1 L4 replaying shield-generator phase: hits=(%d,%d) protected=(%d,%d)",
+ (int)_shieldGenHitsA, (int)_shieldGenHitsB,
+ (int)_protectedTargetA, (int)_protectedTargetB);
+ replayingShieldGeneratorPhase = true;
+ }
_protectedTargetA = 0;
_protectedTargetB = 0;
if (shouldAbortGameFlow())
return false;
- if (_health >= 0) {
+ if (_health >= 0 && shieldGeneratorsDestroyed) {
// Phase 2: torpedo run. The DOS loop enables torpedo mode at frame
// 0x3E and exits early as soon as killCount becomes nonzero.
_activeGameOpcode = 0;
@@ -572,7 +594,7 @@ bool InsaneRebel1::runLevel4() {
return false;
}
- if (_health >= 0) {
+ if (_health >= 0 && shieldGeneratorsDestroyed) {
// L4END1 = torpedo hit, L4END2 = torpedo missed
const bool torpedoHit = (_killCount != 0);
playChapterCompleteCinematic(torpedoHit ? "LVL4/L4END1.ANM" : "LVL4/L4END2.ANM",
@@ -1290,11 +1312,31 @@ bool InsaneRebel1::runLevel14() {
_level14SuccessFrames = 0;
// Phase 1: targeting surface cannons
- playInteractiveVideo("LVL14/L14PLAY.ANM");
+ bool level14Phase1Complete = false;
+ bool replayingLevel14Phase1 = false;
+ while (!shouldAbortGameFlow()) {
+ _preserveInteractiveRuntimeState = replayingLevel14Phase1;
+ playInteractiveVideo("LVL14/L14PLAY.ANM");
+ _preserveInteractiveRuntimeState = false;
+ if (shouldAbortGameFlow())
+ return false;
+
+ if (_health < 0)
+ break;
+
+ level14Phase1Complete = _level14SuccessFrames >= 0x3C;
+ if (level14Phase1Complete)
+ break;
+
+ debugC(DEBUG_INSANE, "RA1 L14 replaying phase 1: successFrames=%d targetsDestroyed=%d",
+ (int)_level14SuccessFrames, areLevel14Phase1TargetsDestroyed() ? 1 : 0);
+ replayingLevel14Phase1 = true;
+ }
if (shouldAbortGameFlow())
return false;
- if (_health >= 0) {
+ bool level14Phase2Complete = false;
+ if (_health >= 0 && level14Phase1Complete) {
// Phase 2: exhaust port approach
_activeGameOpcode = 0;
_gameLatch5D = 0;
@@ -1308,25 +1350,48 @@ bool InsaneRebel1::runLevel14() {
_level14Play2BSpliced = false;
_level14Play2BSpliceFrame = 0;
- playInteractiveVideo("LVL14/L14PLAY2.ANM");
- if (shouldAbortGameFlow())
- return false;
-
- if (_health >= 0 && _level14Play2BSplicePending) {
- const int32 spliceFrame = _level14Play2BSpliceFrame;
- _level14Play2BSplicePending = false;
-
- // DOS queues L14PLY2B from the L14PLAY2 loop with startFrame
- // equal to L14PLAY2's old timeline frame. L14PLY2B itself is the
- // continuation clip, so the port starts it from frame 0 but uses
- // the non-zero frame argument to preserve gameplay/video state.
- playInteractiveVideo("LVL14/L14PLY2B.ANM", spliceFrame);
+ const char *level14Phase2Video = "LVL14/L14PLAY2.ANM";
+ bool replayingLevel14Phase2 = false;
+ while (!shouldAbortGameFlow()) {
+ _preserveInteractiveRuntimeState = replayingLevel14Phase2;
+ playInteractiveVideo(level14Phase2Video);
+ _preserveInteractiveRuntimeState = false;
if (shouldAbortGameFlow())
return false;
+
+ if (_health < 0)
+ break;
+
+ if (_level14Play2BSplicePending) {
+ const int32 spliceFrame = _level14Play2BSpliceFrame;
+ _level14Play2BSplicePending = false;
+ level14Phase2Video = "LVL14/L14PLY2B.ANM";
+
+ // DOS queues L14PLY2B from the L14PLAY2 loop with startFrame
+ // equal to L14PLAY2's old timeline frame. L14PLY2B itself is the
+ // continuation clip, so the port starts it from frame 0 but uses
+ // the non-zero frame argument to preserve gameplay/video state.
+ playInteractiveVideo(level14Phase2Video, spliceFrame);
+ if (shouldAbortGameFlow())
+ return false;
+ if (_health < 0)
+ break;
+ }
+
+ level14Phase2Complete = _level14SuccessFrames >= 0x3C;
+ if (level14Phase2Complete)
+ break;
+
+ debugC(DEBUG_INSANE, "RA1 L14 replaying phase 2: video=%s successFrames=%d targetsDestroyed=%d",
+ level14Phase2Video, (int)_level14SuccessFrames,
+ areLevel14Phase2TargetsDestroyed() ? 1 : 0);
+ replayingLevel14Phase2 = true;
}
+ if (shouldAbortGameFlow())
+ return false;
}
- if (_health >= 0) {
+ if (_health >= 0 && level14Phase2Complete) {
playChapterCompleteCinematic("LVL14/L14END.ANM", 14, 0x69, 5,
nullptr, nullptr, 0, nullptr, nullptr, 0, _difficulty + 10);
return !shouldAbortGameFlow();
@@ -1612,8 +1677,8 @@ void InsaneRebel1::restoreInteractiveVideoAudioState() {
void InsaneRebel1::setupInteractiveVideoState(int32 startFrame) {
const bool level7RouteSplice = (_currentLevel == 6 && _levelRouteIndex > 0);
const bool resumingRoute = startFrame > 0;
- const bool preserveRuntimeState = resumingRoute || level7RouteSplice;
- const bool preserveVideoState = resumingRoute && !level7RouteSplice;
+ const bool preserveRuntimeState = _preserveInteractiveRuntimeState || resumingRoute || level7RouteSplice;
+ const bool preserveVideoState = !_preserveInteractiveRuntimeState && resumingRoute && !level7RouteSplice;
SmushPlayer *splayer = _vm->_splayer;
_player = splayer;
@@ -1689,11 +1754,12 @@ void InsaneRebel1::resolveSeek(const char *filename, int32 startFrame, int32 &vi
void InsaneRebel1::captureInteractiveVideoInput() {
const bool level7RouteSplice = (_currentLevel == 6 && _levelRouteIndex > 0);
+ const bool preserveInputState = _preserveInteractiveRuntimeState || level7RouteSplice;
// Center mouse, hide system cursor (we draw our own), lock mouse to window.
- // Level 7 route splices happen inside one original gameplay loop, so keep
- // the current input state instead of recentering between route clips.
- if (!level7RouteSplice) {
+ // Some replays happen inside one original gameplay loop, so keep the current
+ // input state instead of recentering between route clips.
+ if (!preserveInputState) {
// On touchscreen devices the DOS recenter-the-cursor aiming model does not apply;
// warping/locking the system mouse there injects spurious motion that drifts
// direct touch aiming and on-screen controls.
@@ -1723,12 +1789,15 @@ void InsaneRebel1::playInteractiveVideoFile(const char *filename, int32 videoOff
// Play interactive gameplay video (with ship physics + HUD).
void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame) {
debugC(DEBUG_INSANE, "InsaneRebel1::playInteractiveVideo('%s', startFrame=%d)", filename, startFrame);
- if (shouldAbortGameFlow())
+ if (shouldAbortGameFlow()) {
+ _preserveInteractiveRuntimeState = false;
return;
+ }
int32 videoStartFrame = 0;
int32 videoOffset = 0;
- const bool preserveRuntimeState = (startFrame > 0) || (_currentLevel == 6 && _levelRouteIndex > 0);
+ const bool preserveRuntimeState = _preserveInteractiveRuntimeState ||
+ (startFrame > 0) || (_currentLevel == 6 && _levelRouteIndex > 0);
if (!preserveRuntimeState)
resetInteractiveVideoAudio();
@@ -1737,6 +1806,7 @@ void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame)
captureInteractiveVideoInput();
playInteractiveVideoFile(filename, videoOffset, videoStartFrame);
releaseInteractiveVideoInput();
+ _preserveInteractiveRuntimeState = false;
}
} // End of namespace Scumm
Commit: b820b7a63075dad02d735a88ac729b5cf73d130d
https://github.com/scummvm/scummvm/commit/b820b7a63075dad02d735a88ac729b5cf73d130d
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T16:51:26+02:00
Commit Message:
SCUMM: RA1: improve shift + S to skip some sequences that have wining conditions
Changed paths:
engines/scumm/insane/rebel1/menu.cpp
engines/scumm/insane/rebel1/rebel.cpp
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/runlevels.cpp
diff --git a/engines/scumm/insane/rebel1/menu.cpp b/engines/scumm/insane/rebel1/menu.cpp
index 7677cf0d985..45eb5a1d6e5 100644
--- a/engines/scumm/insane/rebel1/menu.cpp
+++ b/engines/scumm/insane/rebel1/menu.cpp
@@ -895,6 +895,7 @@ bool InsaneRebel1::notifyEvent(const Common::Event &event) {
if (_interactiveVideoActive && !_menuActive &&
event.kbd.keycode == Common::KEYCODE_s &&
event.kbd.hasFlags(Common::KBD_SHIFT)) {
+ _interactiveVideoCheatSkipped = true;
_vm->_smushVideoShouldFinish = true;
return true;
}
diff --git a/engines/scumm/insane/rebel1/rebel.cpp b/engines/scumm/insane/rebel1/rebel.cpp
index 4cfee698554..4fd79f1c7eb 100644
--- a/engines/scumm/insane/rebel1/rebel.cpp
+++ b/engines/scumm/insane/rebel1/rebel.cpp
@@ -318,6 +318,7 @@ InsaneRebel1::InsaneRebel1(ScummEngine_v7 *scumm) : Insane(), _vm(scumm) {
_unlockAllLevels = ConfMan.getBool("rebel1_unlock_all");
_interactiveVideoActive = false;
_preserveInteractiveRuntimeState = false;
+ _interactiveVideoCheatSkipped = false;
_restoreInteractiveVideoAudioState = false;
memset(_savedInteractiveVideoTrackState, 0, sizeof(_savedInteractiveVideoTrackState));
memset(_savedInteractiveVideoTrackGroupId, 0, sizeof(_savedInteractiveVideoTrackGroupId));
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index 8ff100263bc..f74d6dd5d69 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -522,6 +522,7 @@ private:
// True only while an interactive gameplay SMUSH is running.
bool _interactiveVideoActive;
bool _preserveInteractiveRuntimeState;
+ bool _interactiveVideoCheatSkipped;
// Path branching for levels with left/right alternative videos.
// Original sets nextSceneA/nextSceneB when GAME 0x07 counter == 394 (0x18A).
diff --git a/engines/scumm/insane/rebel1/runlevels.cpp b/engines/scumm/insane/rebel1/runlevels.cpp
index 7f4e7ce0d41..215fe75864f 100644
--- a/engines/scumm/insane/rebel1/runlevels.cpp
+++ b/engines/scumm/insane/rebel1/runlevels.cpp
@@ -412,7 +412,7 @@ bool InsaneRebel1::runLevel1() {
if (_health < 0)
break;
- if (_killCount > 4) {
+ if (_killCount > 4 || _interactiveVideoCheatSkipped) {
const char *pathText = _rightPathSelected ? "Path Taken: Hard" : "Path Taken: Easy";
const int pathBonus = _rightPathSelected ? _tuning.bonus * 3 : 0;
char accuracyText[80];
@@ -563,9 +563,9 @@ bool InsaneRebel1::runLevel4() {
if (_health < 0)
break;
- shieldGeneratorsDestroyed =
- _protectedTargetA == 0 && _protectedTargetB == 0 &&
- _shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30;
+ shieldGeneratorsDestroyed = _interactiveVideoCheatSkipped ||
+ (_protectedTargetA == 0 && _protectedTargetB == 0 &&
+ _shieldGenHitsA > 0x30 + 0x3C && _shieldGenHitsB > 0x30);
if (shieldGeneratorsDestroyed)
break;
@@ -640,7 +640,7 @@ bool InsaneRebel1::runLevel5() {
return false;
}
- if (_killCount <= 2) {
+ if (_killCount <= 2 && !_interactiveVideoCheatSkipped) {
if (_lives > 0) {
_lives--;
playCinematic("LVL5/L5RETRY.ANM");
@@ -862,6 +862,11 @@ bool InsaneRebel1::runLevel8() {
if (_health < 0)
break;
+ if (_interactiveVideoCheatSkipped) {
+ _walkerHealth = 0;
+ break;
+ }
+
if (_walkerHealth <= 0)
break;
@@ -941,6 +946,8 @@ bool InsaneRebel1::runLevel9() {
playInteractiveVideo(filename);
if (shouldAbortGameFlow() || _health < 0)
return -1;
+ if (_interactiveVideoCheatSkipped)
+ return (_shipPosX < kRA1CenterX) ? 0 : 1;
if (_killCount > 0)
return (_shipPosX < kRA1CenterX) ? 0 : 1;
@@ -1011,7 +1018,7 @@ bool InsaneRebel1::runLevel9() {
if (_health < 0)
break;
- if (_killCount < 15) {
+ if (_killCount < 15 && !_interactiveVideoCheatSkipped) {
playInteractiveVideo("LVL9/L9PLAY3B.ANM");
if (shouldAbortGameFlow())
return false;
@@ -1164,7 +1171,7 @@ bool InsaneRebel1::runLevel11() {
break;
// Original: killCount > 4 means pass
- if (_killCount > 4)
+ if (_killCount > 4 || _interactiveVideoCheatSkipped)
break;
// Not enough kills â retry
@@ -1223,7 +1230,7 @@ bool InsaneRebel1::runLevel12() {
if (shouldAbortGameFlow())
return false;
- if (_levelGameplayPhase == 1) {
+ if (_levelGameplayPhase == 1 && !_interactiveVideoCheatSkipped) {
playCinematic("LVL12/L12RETRY.ANM");
if (shouldAbortGameFlow())
return false;
@@ -1324,7 +1331,7 @@ bool InsaneRebel1::runLevel14() {
if (_health < 0)
break;
- level14Phase1Complete = _level14SuccessFrames >= 0x3C;
+ level14Phase1Complete = _interactiveVideoCheatSkipped || _level14SuccessFrames >= 0x3C;
if (level14Phase1Complete)
break;
@@ -1362,6 +1369,11 @@ bool InsaneRebel1::runLevel14() {
if (_health < 0)
break;
+ if (_interactiveVideoCheatSkipped) {
+ level14Phase2Complete = true;
+ break;
+ }
+
if (_level14Play2BSplicePending) {
const int32 spliceFrame = _level14Play2BSpliceFrame;
_level14Play2BSplicePending = false;
@@ -1378,7 +1390,7 @@ bool InsaneRebel1::runLevel14() {
break;
}
- level14Phase2Complete = _level14SuccessFrames >= 0x3C;
+ level14Phase2Complete = _interactiveVideoCheatSkipped || _level14SuccessFrames >= 0x3C;
if (level14Phase2Complete)
break;
@@ -1454,7 +1466,7 @@ bool InsaneRebel1::runLevel15() {
return false;
}
- if (_health >= 0 && !_torpedoFired) {
+ if (_health >= 0 && !_torpedoFired && !_interactiveVideoCheatSkipped) {
debugC(DEBUG_INSANE, "InsaneRebel1::runLevel15: Level 15 torpedo run ended without exhaust-port hit");
return false;
}
@@ -1791,9 +1803,11 @@ void InsaneRebel1::playInteractiveVideo(const char *filename, int32 startFrame)
debugC(DEBUG_INSANE, "InsaneRebel1::playInteractiveVideo('%s', startFrame=%d)", filename, startFrame);
if (shouldAbortGameFlow()) {
_preserveInteractiveRuntimeState = false;
+ _interactiveVideoCheatSkipped = false;
return;
}
+ _interactiveVideoCheatSkipped = false;
int32 videoStartFrame = 0;
int32 videoOffset = 0;
const bool preserveRuntimeState = _preserveInteractiveRuntimeState ||
Commit: d9fc895bf5e6e9c43ae00274ae88053d317af2d4
https://github.com/scummvm/scummvm/commit/d9fc895bf5e6e9c43ae00274ae88053d317af2d4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-04T17:11:24+02:00
Commit Message:
SCUMM: RA1: added missing overlay in level 5
Changed paths:
engines/scumm/insane/rebel1/rebel.h
engines/scumm/insane/rebel1/render.cpp
engines/scumm/smush/rebel/smush_player_ra1.cpp
diff --git a/engines/scumm/insane/rebel1/rebel.h b/engines/scumm/insane/rebel1/rebel.h
index f74d6dd5d69..26dc27c6014 100644
--- a/engines/scumm/insane/rebel1/rebel.h
+++ b/engines/scumm/insane/rebel1/rebel.h
@@ -111,6 +111,7 @@ public:
bool isTouchscreenActive() const;
void setFrameHasGameChunk(bool hasGameChunk) { _frameHasGameChunk = hasGameChunk; }
int getCurrentLevel() const { return _currentLevel; }
+ int getLevelGameplayPhase() const { return _levelGameplayPhase; }
uint16 getActiveGameOpcode() const { return _activeGameOpcode; }
uint16 getEffectiveGameOpcode() const;
bool hasFrameGameOpcode(uint16 opcode) const {
@@ -245,6 +246,7 @@ private:
bool drawTargetBoxes);
void handleLevel14Play2BSplice(int32 curFrame, int32 maxFrame);
void renderLevel7RouteOverlays(byte *dst, int pitch, int width, int height);
+ void renderLevel5Part2Overlay(byte *dst, int pitch, int width, int height, int32 curFrame);
void renderLevel11HitsOverlay(byte *dst, int pitch, int width, int height);
void resetEnemyShotSlots();
void renderLevel13EnemyShots(byte *dst, int pitch, int width, int height);
diff --git a/engines/scumm/insane/rebel1/render.cpp b/engines/scumm/insane/rebel1/render.cpp
index f428e495c39..1a17b10eb9f 100644
--- a/engines/scumm/insane/rebel1/render.cpp
+++ b/engines/scumm/insane/rebel1/render.cpp
@@ -797,6 +797,9 @@ void InsaneRebel1::procPostRendering(byte *renderBitmap, int32 codecparam, int32
renderExplosions(renderBitmap, pitch, width, height);
handleLevel14Play2BSplice(curFrame, maxFrame);
+ if (_currentLevel == 4 && _levelGameplayPhase == 2)
+ renderLevel5Part2Overlay(renderBitmap, pitch, width, height, curFrame);
+
if (_currentLevel == 10)
renderLevel11HitsOverlay(renderBitmap, pitch, width, height);
@@ -1026,6 +1029,35 @@ void InsaneRebel1::renderLevel11HitsOverlay(byte *dst, int pitch, int width, int
drawFontBankString(dst, pitch, width, height, 0x119, 0x16, hitsStr);
}
+// renderLevel5Part2Overlay â RunLevel5Flow (0x176D0-0x1777E) draws the
+// part-2 instruction reveal, then switches to the live target count.
+void InsaneRebel1::renderLevel5Part2Overlay(byte *dst, int pitch, int width, int height, int32 curFrame) {
+ if (_hudFontBank.numSprites <= 0 && _techFontBank.numSprites <= 0)
+ return;
+
+ const int viewportX = _player ? ra1Player()->_ra1ViewportOffsetX : 0;
+ const int viewportY = _player ? ra1Player()->_ra1ViewportOffsetY : 0;
+ int16 overlayX = 0x88;
+ int16 overlayY = 0x8C;
+ projectGameplayPoint(overlayX, overlayY);
+ overlayX = (int16)(0x88 - ((overlayX - 0x88) >> 2));
+ overlayY = (int16)(0x8C - ((overlayY - 0x8C) >> 2));
+
+ if (curFrame < 0x32) {
+ const char instructionText[] = "<<SHOOT TARGETS FOR BONUS";
+ const int revealChars = CLIP<int>((int)curFrame, 0, (int)sizeof(instructionText) - 1);
+ char revealedText[sizeof(instructionText)];
+ Common::strlcpy(revealedText, instructionText, revealChars + 1);
+ drawFontBankString(dst, pitch, width, height,
+ viewportX + overlayX - 0x27, viewportY + overlayY, revealedText);
+ } else {
+ char hitsStr[16];
+ Common::sprintf_s(hitsStr, "<<HITS: %d", (int)_killCount);
+ drawFontBankString(dst, pitch, width, height,
+ viewportX + overlayX, viewportY + overlayY, hitsStr);
+ }
+}
+
void InsaneRebel1::resetEnemyShotSlots() {
memset(_enemyShotSlots, 0, sizeof(_enemyShotSlots));
}
diff --git a/engines/scumm/smush/rebel/smush_player_ra1.cpp b/engines/scumm/smush/rebel/smush_player_ra1.cpp
index 880eb987be9..19add73b0d5 100644
--- a/engines/scumm/smush/rebel/smush_player_ra1.cpp
+++ b/engines/scumm/smush/rebel/smush_player_ra1.cpp
@@ -241,13 +241,16 @@ bool SmushPlayerRebel1::handleGameFetch(int32 subSize, Common::SeekableReadStrea
if (rebel1->isInteractiveVideoActive()) {
const uint16 gameOp = rebel1->getActiveGameOpcode();
const bool fullWidthStoredPatch = (_storedFobjWidth == _vm->_screenWidth);
- const bool level8WalkerPatch = (gameOp == 0x0B && rebel1->getCurrentLevel() == 7);
+ const bool projectedCockpitPatch = (gameOp == 0x0B &&
+ ((rebel1->getCurrentLevel() == 4 && rebel1->getLevelGameplayPhase() == 2) ||
+ rebel1->getCurrentLevel() == 7));
// Keep the direct viewport placement used by the existing 0x0B
- // compatibility path, except for Level 8's walker cockpit patch.
+ // compatibility path, except for the Level 5 part 2 and Level 8
+ // cockpit patches.
// DOS FTCH goes through FUN_28D0A, which sets DispatchFobjCodec
- // flag 0x800; Level 8 depends on that quarter-projection so the
- // cockpit and RunLevel8Flow indicators move together.
- if (fullWidthStoredPatch || (gameOp == 0x0B && !level8WalkerPatch) ||
+ // flag 0x800; these cockpit patches depend on that quarter-projection
+ // so the cockpit and flow-script indicators move together.
+ if (fullWidthStoredPatch || (gameOp == 0x0B && !projectedCockpitPatch) ||
gameOp == 0x19 || gameOp == 0x1A) {
left += _ra1ViewportOffsetX;
top += _ra1ViewportOffsetY;
More information about the Scummvm-git-logs
mailing list