[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