[Scummvm-git-logs] scummvm master -> 72bea8fb646f8cd2cb874f9f364b093625754015
neuromancer
noreply at scummvm.org
Wed Jun 17 12:02:22 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
357a211157 SCUMM: RA2: level 6 (part 1) shield mechanic implemented properly
72bea8fb64 SCUMM: RA2: level 13 (part 2) reactor mechanic implemented properly
Commit: 357a211157eb2fe1a58b2177ea8fa32d8431e584
https://github.com/scummvm/scummvm/commit/357a211157eb2fe1a58b2177ea8fa32d8431e584
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-17T14:01:07+02:00
Commit Message:
SCUMM: RA2: level 6 (part 1) shield mechanic implemented properly
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 eb59a01ad7e..30473c7dbec 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -78,8 +78,14 @@ void InsaneRebel2::procPreRendering(byte *renderBitmap) {
// Reset opcode 6 init flag at the start of each new video.
// This ensures the per-wave init (clearBit, link table reset, wave state)
// fires exactly once per wave video, not every frame.
+ //
+ // Exception: seamless continuation segments (flag 0x40, e.g. the looping 06PLAY1B attack
+ // run) keep the init flag set, otherwise the wave init's clearBit(0) would resurrect
+ // shield targets the player already destroyed, resetting the shield on every loop.
if (_player && _player->_frame == 0) {
- _rebelOp6Initialized = false;
+ const bool shieldContinuation = _rebelShieldGateActive && (_player->_curVideoFlags & 0x40) != 0;
+ if (!shieldContinuation)
+ _rebelOp6Initialized = false;
}
// For Level 2 handler 8 gameplay, restore the background BEFORE FOBJ decoding.
@@ -421,6 +427,10 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
if (idx >= 0 && idx < 10) {
_rebelValueCounters[idx]++;
_rebelLastCounter = _rebelValueCounters[idx];
+ // Track that this target feeds value-counter[idx] so destroying it
+ // decrements the shield gauge. Only while a shield gate is active.
+ if (_rebelShieldGateActive && targetId >= 0 && targetId < 512)
+ _rebelGaugeSlot[targetId] = (int8)idx;
debugC(DEBUG_INSANE, "IACT Opcode2: Increment VAL counter[%d] -> %d (target=%d)", value, _rebelValueCounters[idx], targetId);
}
}
@@ -431,6 +441,8 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
if (!isBitSet(targetId)) {
_rebelMaskCounters[slot]++;
_rebelLastCounter = _rebelMaskCounters[slot];
+ if (_rebelShieldGateActive && targetId >= 0 && targetId < 512)
+ _rebelGaugeSlot[targetId] = (int8)(10 + slot);
debugC(DEBUG_INSANE, "IACT Opcode2: Increment MASK counter[%d] -> %d (target=%d)", slot, _rebelMaskCounters[slot], targetId);
}
}
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 35c0dd5186b..904c8341e85 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -239,6 +239,11 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
}
_rebelLastCounter = 0;
+ for (int i = 0; i < 512; ++i)
+ _rebelGaugeSlot[i] = -1;
+ _rebelShieldGateActive = false;
+ _rebelShieldDestroyed = false;
+
_difficulty = 1; // Default to Medium.
_targetLockTimer = 0; // DAT_00443676 equivalent
@@ -1744,6 +1749,23 @@ int32 InsaneRebel2::processMouse() {
// Disable self (prevents sprite from rendering via SKIP chunks)
setBit(it->id);
+ // Shield hit-point gauge: if this target feeds a gauge counter, destroying it
+ // decrements that counter; the shield is destroyed when it reaches 0.
+ if (_rebelShieldGateActive && it->id >= 0 && it->id < 512 && _rebelGaugeSlot[it->id] >= 0) {
+ int slot = _rebelGaugeSlot[it->id];
+ short *counter = (slot < 10) ? &_rebelValueCounters[slot]
+ : &_rebelMaskCounters[slot - 10];
+ if (*counter > 0) {
+ (*counter)--;
+ _rebelLastCounter = *counter;
+ if (*counter == 0) {
+ _rebelShieldDestroyed = true;
+ debugC(DEBUG_INSANE, "Shield destroyed (gauge slot %d depleted by target %d)", slot, it->id);
+ }
+ }
+ _rebelGaugeSlot[it->id] = -1; // consume: avoid double-counting
+ }
+
// Set enemy type bit in wave state (FUN_004028c5 line 74)
// DAT_0047ab98 |= 1 << (enemyType & 0x1f)
// This tracks which enemy GROUPS have been killed in this wave
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index ee2924471fa..72365721461 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -962,6 +962,13 @@ public:
short _rebelMaskCounters[10]; // Index 1..9 used; index 0 unused
int _rebelLastCounter; // Mirrors DAT_0047ab90 (last updated counter)
+ // Shield hit-point gauge: opcode-2 sets up a per-target counter; destroying a tracked
+ // target decrements it, and the looping attack run ends when it reaches 0.
+ int8 _rebelGaugeSlot[512]; // per target ID: -1 none; 0..9 value-counter; 10+slot mask-counter
+ bool _rebelShieldGateActive; // true while a shield-gated looping segment is playing
+ bool _rebelShieldDestroyed; // set when a tracked gauge counter reaches 0 during the gate
+ void resetShieldGauge();
+
// ---------------------------------------------------------------------------
// Handler 0x26 Turret Shot System
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 6f3fa8ed5a7..778f0c0baee 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2780,6 +2780,10 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
updatePostRenderScroll(width, height);
updatePostRenderDeath();
+ // End the looping attack-run segment once the shield's hit-point gauge is depleted.
+ if (_rebelShieldGateActive && _rebelShieldDestroyed)
+ _vm->_smushVideoShouldFinish = true;
+
// Use video content coordinates, NOT oversized low-res gameplay-buffer coordinates.
const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
const int videoWidth = 320 * hudScale;
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 09a450264a7..aa37df7e2ef 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -332,6 +332,18 @@ void InsaneRebel2::resetLevelWaveState() {
_rebelWaveState = 0;
}
+// resetShieldGauge -- Clear the shield hit-point gauge before a looping attack-run segment.
+void InsaneRebel2::resetShieldGauge() {
+ for (int i = 0; i < 10; ++i) {
+ _rebelValueCounters[i] = 0;
+ _rebelMaskCounters[i] = 0;
+ }
+ for (int i = 0; i < 512; ++i)
+ _rebelGaugeSlot[i] = -1;
+ _rebelLastCounter = -1; // non-zero sentinel: gauge not yet depleted
+ _rebelShieldDestroyed = false;
+}
+
void InsaneRebel2::resetExplosions() {
for (uint i = 0; i < ARRAYSIZE(_explosions); ++i) {
_explosions[i].active = false;
@@ -933,15 +945,29 @@ int InsaneRebel2::runLevel6() {
// DAT_0047ab9c = 0xffffffff â init phase state
_rebelPhaseState = 0xffffffff;
- // ----- PHASE 1 -----
+ // ----- PHASE 1: shield attack run -----
+ // Play 06PLAY1 (approach), then loop the 06PLAY1B continuation until the player
+ // destroys the shield (its hit gauge reaches 0) or dies.
_rebelLevelType = 5; // DAT_0047a7f8 = 5
_currentPhase = 1;
- debugC(DEBUG_INSANE, "Level 6 Phase 1");
- if (!playLevelSegment("LEV06/06PLAY1.SAN", 0x28))
+ debugC(DEBUG_INSANE, "Level 6 Phase 1 (shield attack run)");
+ resetShieldGauge();
+ _rebelShieldGateActive = true;
+
+ if (!playLevelSegment("LEV06/06PLAY1.SAN", 0x28)) {
+ _rebelShieldGateActive = false;
return kLevelQuit;
- // TODO: Mid-level switch at frame 0x2a8 to 06PLAY1B.SAN (flags 0x468)
- // + score checkpoint (FUN_00407f55) â needs per-frame callback
+ }
+
+ while (_playerShield > 0 && !_rebelShieldDestroyed && !_vm->shouldQuit()) {
+ resetShieldGauge();
+ if (!playLevelSegment("LEV06/06PLAY1B.SAN", 0x468)) {
+ _rebelShieldGateActive = false;
+ return kLevelQuit;
+ }
+ }
+ _rebelShieldGateActive = false;
if (_playerShield <= 0) {
// Died in phase 1
Commit: 72bea8fb646f8cd2cb874f9f364b093625754015
https://github.com/scummvm/scummvm/commit/72bea8fb646f8cd2cb874f9f364b093625754015
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-17T14:01:07+02:00
Commit Message:
SCUMM: RA2: level 13 (part 2) reactor mechanic implemented properly
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 30473c7dbec..02493b26fbc 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -429,8 +429,11 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
_rebelLastCounter = _rebelValueCounters[idx];
// Track that this target feeds value-counter[idx] so destroying it
// decrements the shield gauge. Only while a shield gate is active.
- if (_rebelShieldGateActive && targetId >= 0 && targetId < 512)
+ if (_rebelShieldGateActive && targetId >= 0 && targetId < 512) {
_rebelGaugeSlot[targetId] = (int8)idx;
+ _rebelGaugeArmed = true;
+ _rebelLastArmedSlot = idx;
+ }
debugC(DEBUG_INSANE, "IACT Opcode2: Increment VAL counter[%d] -> %d (target=%d)", value, _rebelValueCounters[idx], targetId);
}
}
@@ -441,8 +444,11 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
if (!isBitSet(targetId)) {
_rebelMaskCounters[slot]++;
_rebelLastCounter = _rebelMaskCounters[slot];
- if (_rebelShieldGateActive && targetId >= 0 && targetId < 512)
+ if (_rebelShieldGateActive && targetId >= 0 && targetId < 512) {
_rebelGaugeSlot[targetId] = (int8)(10 + slot);
+ _rebelGaugeArmed = true;
+ _rebelLastArmedSlot = 10 + slot;
+ }
debugC(DEBUG_INSANE, "IACT Opcode2: Increment MASK counter[%d] -> %d (target=%d)", slot, _rebelMaskCounters[slot], targetId);
}
}
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 904c8341e85..6b574ec9675 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -243,6 +243,9 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
_rebelGaugeSlot[i] = -1;
_rebelShieldGateActive = false;
_rebelShieldDestroyed = false;
+ _rebelReactorMode = false;
+ _rebelGaugeArmed = false;
+ _rebelLastArmedSlot = -1;
_difficulty = 1; // Default to Medium.
_targetLockTimer = 0; // DAT_00443676 equivalent
@@ -1758,7 +1761,9 @@ int32 InsaneRebel2::processMouse() {
if (*counter > 0) {
(*counter)--;
_rebelLastCounter = *counter;
- if (*counter == 0) {
+ // Level 6 ends on any group emptying; Level 13's reactor is gated in
+ // procPostRendering instead, so early obstacle groups don't complete it.
+ if (*counter == 0 && !_rebelReactorMode) {
_rebelShieldDestroyed = true;
debugC(DEBUG_INSANE, "Shield destroyed (gauge slot %d depleted by target %d)", slot, it->id);
}
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index 72365721461..5fb614c8009 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -966,7 +966,10 @@ public:
// target decrements it, and the looping attack run ends when it reaches 0.
int8 _rebelGaugeSlot[512]; // per target ID: -1 none; 0..9 value-counter; 10+slot mask-counter
bool _rebelShieldGateActive; // true while a shield-gated looping segment is playing
- bool _rebelShieldDestroyed; // set when a tracked gauge counter reaches 0 during the gate
+ bool _rebelShieldDestroyed; // set when the shield/reactor is destroyed during the gate
+ bool _rebelReactorMode; // Level 13: finale ends when the last armed group is cleared
+ bool _rebelGaugeArmed; // at least one gauge group has been set up this attempt
+ int _rebelLastArmedSlot; // counter slot of the most recently armed group (-1 = none)
void resetShieldGauge();
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 778f0c0baee..b378b56093a 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2780,9 +2780,21 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
updatePostRenderScroll(width, height);
updatePostRenderDeath();
- // End the looping attack-run segment once the shield's hit-point gauge is depleted.
- if (_rebelShieldGateActive && _rebelShieldDestroyed)
- _vm->_smushVideoShouldFinish = true;
+ // End the looping attack-run segment once the shield/reactor is destroyed.
+ if (_rebelShieldGateActive) {
+ // Level 13: the finale (continuation segment, flag 0x40) ends when the last armed
+ // group (the reactor) is depleted; the approach segment plays fully.
+ if (_rebelReactorMode && _rebelGaugeArmed && _rebelLastArmedSlot >= 0 &&
+ (_player->_curVideoFlags & 0x40) != 0) {
+ const int slot = _rebelLastArmedSlot;
+ const short remaining = (slot < 10) ? _rebelValueCounters[slot]
+ : _rebelMaskCounters[slot - 10];
+ if (remaining <= 0)
+ _rebelShieldDestroyed = true;
+ }
+ if (_rebelShieldDestroyed)
+ _vm->_smushVideoShouldFinish = true;
+ }
// Use video content coordinates, NOT oversized low-res gameplay-buffer coordinates.
const int hudScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index aa37df7e2ef..5f1e95858b4 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -342,6 +342,8 @@ void InsaneRebel2::resetShieldGauge() {
_rebelGaugeSlot[i] = -1;
_rebelLastCounter = -1; // non-zero sentinel: gauge not yet depleted
_rebelShieldDestroyed = false;
+ _rebelGaugeArmed = false;
+ _rebelLastArmedSlot = -1;
}
void InsaneRebel2::resetExplosions() {
@@ -1937,25 +1939,31 @@ int InsaneRebel2::runLevel13() {
clearBit(0);
- // Phase A: Main escape flight (13PLAY_A.SAN)
- // Original: FUN_0041f4d0("13PLAY_A.SAN", 0x28, -1, -1, 0)
- // First inner loop runs until frame reaches maxFrame-10
- // Then Phase B (13PLAY_B.SAN, flags 0x468) plays at that exact frame
- // The 0x468 flags indicate seamless mid-video transition.
- if (!playLevelSegment("LEV13/13PLAY_A.SAN", 0x28))
+ // Phase A: full approach flight; the reactor gauge is tracked but A plays to the end.
+ resetShieldGauge();
+ _rebelShieldGateActive = true;
+ _rebelReactorMode = true;
+ if (!playLevelSegment("LEV13/13PLAY_A.SAN", 0x28)) {
+ _rebelShieldGateActive = false;
+ _rebelReactorMode = false;
return kLevelQuit;
+ }
- // If alive after Phase A, play Phase B (reactor destruction loop)
- // Original: at frame == maxFrame-10, play 13PLAY_B.SAN (0x468)
- // Then loop while (DAT_0047ab90 != 0 || DAT_0047ab7c != 0)
- // Play B as a sequential video. The IACT callbacks manage the reactor
- // target state through opcode interactions.
- if (_playerShield > 0) {
- if (!playLevelSegment("LEV13/13PLAY_B.SAN", 0x468))
+ // Reactor finale: loop 13PLAY_B until the reactor is destroyed or the player dies.
+ // The gauge persists from A (no reset) so hits carry over; finaleGuard caps the loop.
+ int finaleGuard = 0;
+ while (_playerShield > 0 && !_rebelShieldDestroyed && !_vm->shouldQuit() && finaleGuard < 60) {
+ ++finaleGuard;
+ if (!playLevelSegment("LEV13/13PLAY_B.SAN", 0x468)) {
+ _rebelShieldGateActive = false;
+ _rebelReactorMode = false;
return kLevelQuit;
+ }
}
+ _rebelShieldGateActive = false;
+ _rebelReactorMode = false;
- if (_playerShield > 0) {
+ if (_playerShield > 0 && _rebelShieldDestroyed) {
int accuracy = calculateAccuracy(_rebelKillCounter, _rebelHitCounter);
debugC(DEBUG_INSANE, "Level 13 completed! accuracy=%d%%", accuracy);
playLevelEnd(13);
More information about the Scummvm-git-logs
mailing list