[Scummvm-git-logs] scummvm master -> c14a871dd6b241e0048aeeef8f94015defe8468a

neuromancer noreply at scummvm.org
Thu Jul 2 06:39:02 UTC 2026


This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
5876cedb9b SCUMM: RA2: L2 and L11 starts with the player fully in cover
504aed8ed3 SCUMM: RA2: make sure force field are disabled when destroying all its parts
1e934bfa1c SCUMM: RA2: fidelity fixes for L10 and L15
cbc1f4d7ed SCUMM: RA2: implement timepoints in all the levels
ceb75188de SCUMM: RA2: correctly implemented waves in L2 and L12
c14a871dd6 SCUMM: RA2: added missing gameplay sounds


Commit: 5876cedb9b0cfd4cef9d8ba61ce50d27f350f8c4
    https://github.com/scummvm/scummvm/commit/5876cedb9b0cfd4cef9d8ba61ce50d27f350f8c4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:36+02:00

Commit Message:
SCUMM: RA2: L2 and L11 starts with the player fully in cover

Changed paths:
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index e6aaec7d7a6..520ac299ea5 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -268,10 +268,11 @@ void InsaneRebel2::resetLevelPhaseState(bool clearEnemies) {
 	if (clearEnemies)
 		_enemies.clear();
 
-	if (_selectedChapter == 1 || _selectedChapter == 10) {
-		_rebelAutopilot = 1;
-		_rebelDamageLevel = 5;
-	}
+	// Every phase starts with the player fully in cover. This only has an
+	// effect in the on-foot cover sections (handler 25, levels 2 and 11);
+	// the other levels ignore these values.
+	_rebelAutopilot = 1;
+	_rebelDamageLevel = 5;
 }
 
 void InsaneRebel2::clearEmbeddedHudFrames() {


Commit: 504aed8ed3a0e053c1ea614541065d5ee7b1b39a
    https://github.com/scummvm/scummvm/commit/504aed8ed3a0e053c1ea614541065d5ee7b1b39a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:37+02:00

Commit Message:
SCUMM: RA2: make sure force field are disabled when destroying all its parts

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 3e4eef11df8..c693f280798 100644
--- a/engines/scumm/insane/rebel2/iact.cpp
+++ b/engines/scumm/insane/rebel2/iact.cpp
@@ -295,32 +295,27 @@ void InsaneRebel2::iactRebel2Opcode2(Common::SeekableReadStream &b, int16 par2,
 				}
 			}
 		} else if (value > 99 && value < 110) {
+			int idx = value - 100;
 			if (!isBitSet(targetId)) {
-				int idx = value - 100;
-				if (idx >= 0 && idx < 10) {
-					_rebelValueCounters[idx]++;
-					_rebelLastCounter = _rebelValueCounters[idx];
-					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);
-				}
+				_rebelValueCounters[idx]++;
+				debugC(DEBUG_INSANE, "IACT Opcode2: Increment counter[%d] -> %d (target=%d)", idx, _rebelValueCounters[idx], targetId);
+			}
+			_rebelLastCounter = _rebelValueCounters[idx];
+			if (_rebelShieldGateActive) {
+				_rebelGaugeArmed = true;
+				_rebelLastArmedSlot = idx;
 			}
-
 		} else if (value > 0x3ff) {
 			for (int slot = 1; slot <= 9; ++slot) {
 				if ((value & (1 << (slot - 1))) != 0) {
 					if (!isBitSet(targetId)) {
-						_rebelMaskCounters[slot]++;
-						_rebelLastCounter = _rebelMaskCounters[slot];
-						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);
+						_rebelValueCounters[slot]++;
+						debugC(DEBUG_INSANE, "IACT Opcode2: Increment counter[%d] (mask 0x%x) -> %d (target=%d)", slot, value, _rebelValueCounters[slot], targetId);
+					}
+					_rebelLastCounter = _rebelValueCounters[slot];
+					if (_rebelShieldGateActive) {
+						_rebelGaugeArmed = true;
+						_rebelLastArmedSlot = slot;
 					}
 				}
 			}
@@ -2147,13 +2142,19 @@ void InsaneRebel2::enemyUpdate(byte *renderBitmap, Common::SeekableReadStream &b
 	int16 w = b.readSint16LE();
 	int16 h = b.readSint16LE();
 
-	// Turret surface targets mirror their gauge group state.
+	// Turret surface targets mirror their gauge group state: hidden while the group
+	// counter sits at zero, shown while it is nonzero, with a blink-out (alternating
+	// odd/even ticks) during the 6 frames after the group is depleted.
 	if (par3 == 2 && _rebelHandler == 0x26) {
-		const int surfaceIdx = (par4 >= 100 && par4 < 110) ? (par4 - 100) : -1;
-		if (surfaceIdx >= 0 && _rebelGaugeCleared[surfaceIdx])
-			setBit(enemyId);
-		else
-			clearBit(enemyId);
+		if (par4 >= 100 && par4 < 110) {
+			const int surfaceIdx = par4 - 100;
+			if (_rebelValueCounters[surfaceIdx] == 0 && (_rebelGaugeBlink[surfaceIdx] & 1) == 0)
+				setBit(enemyId);
+			else
+				clearBit(enemyId);
+			if (_rebelGaugeBlink[surfaceIdx] != 0)
+				_rebelGaugeBlink[surfaceIdx]--;
+		}
 		return;
 	}
 
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 3903f0b3f04..935d70e7a75 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -244,19 +244,15 @@ InsaneRebel2::InsaneRebel2(ScummEngine_v7 *scumm) {
 
 	for (int i = 0; i < 10; ++i) {
 		_rebelValueCounters[i] = 0;
-		_rebelMaskCounters[i] = 0;
+		_rebelGaugeBlink[i] = 0;
 	}
 	_rebelLastCounter = 0;
 
-	for (int i = 0; i < 512; ++i)
-		_rebelGaugeSlot[i] = -1;
 	_rebelShieldGateActive = false;
 	_rebelShieldDestroyed = false;
 	_rebelReactorMode = false;
 	_rebelGaugeArmed = false;
 	_rebelLastArmedSlot = -1;
-	for (int i = 0; i < 10; ++i)
-		_rebelGaugeCleared[i] = false;
 
 	_difficulty = 1;
 	_targetLockTimer = 0;
@@ -1610,25 +1606,18 @@ int32 InsaneRebel2::processMouse() {
 
 				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) {
-							if (slot < 10)
-								_rebelGaugeCleared[slot] = true;
-							if (!_rebelReactorMode) {
-								_rebelShieldDestroyed = true;
-								debugC(DEBUG_INSANE, "Shield destroyed (gauge slot %d depleted by target %d)", slot, it->id);
-							}
+				// Shield hit-point gauge (handler 0x26): the target's type field addresses
+				// the gauge group(s) it feeds — 100-109 directly, > 0x3ff as a bitmask —
+				// and destroying it decrements each of them.
+				if (_rebelHandler == 0x26) {
+					if (it->type >= 100 && it->type < 110) {
+						decrementGaugeGroup(it->type - 100, it->id);
+					} else if (it->type > 0x3ff) {
+						for (int slot = 1; slot <= 9; ++slot) {
+							if ((it->type & (1 << (slot - 1))) != 0)
+								decrementGaugeGroup(slot, it->id);
 						}
 					}
-					_rebelGaugeSlot[it->id] = -1;
 				}
 
 				if (it->type > 0 && it->type < 32) {
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index f4ccbf1b8d1..efd13079ae1 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -692,20 +692,18 @@ public:
 	int _rebelViewMode1;
 	int _rebelViewMode2;
 
+	// Turret (0x26) gauge groups, addressed by value 100-109 or bitmask > 0x3ff (bit k-1 -> slot k); surfaces show while nonzero, then blink out.
 	short _rebelValueCounters[10];
-	short _rebelMaskCounters[10];
+	short _rebelGaugeBlink[10];
 	int _rebelLastCounter;
 
-	// 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];
 	bool _rebelShieldGateActive;
 	bool _rebelShieldDestroyed;
 	bool _rebelReactorMode;
 	bool _rebelGaugeArmed;
 	int _rebelLastArmedSlot;
-	bool _rebelGaugeCleared[10];
 	void resetShieldGauge();
+	void decrementGaugeGroup(int slot, int targetId);
 
 	// Handler-specific shot state.
 	struct TurretShot {
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index ddaa6bf2aa6..c7c97513b07 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2415,8 +2415,7 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
 		if (_rebelReactorMode && _rebelGaugeArmed && _rebelLastArmedSlot >= 0 &&
 		    (_player->_curVideoFlags & 0x40) != 0) {
 			const int slot = _rebelLastArmedSlot;
-			const short remaining = (slot < 10) ? _rebelValueCounters[slot]
-			                                    : _rebelMaskCounters[slot - 10];
+			const short remaining = _rebelValueCounters[slot];
 			if (remaining <= 0)
 				_rebelShieldDestroyed = true;
 		}
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 520ac299ea5..d9796d6bf2c 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -296,16 +296,24 @@ void InsaneRebel2::resetLevelWaveState() {
 void InsaneRebel2::resetShieldGauge() {
 	for (int i = 0; i < 10; ++i) {
 		_rebelValueCounters[i] = 0;
-		_rebelMaskCounters[i] = 0;
+		_rebelGaugeBlink[i] = 0;
 	}
-	for (int i = 0; i < 512; ++i)
-		_rebelGaugeSlot[i] = -1;
 	_rebelLastCounter = -1;        // non-zero sentinel: gauge not yet depleted
 	_rebelShieldDestroyed = false;
 	_rebelGaugeArmed = false;
 	_rebelLastArmedSlot = -1;
-	for (int i = 0; i < 10; ++i)
-		_rebelGaugeCleared[i] = false;
+}
+
+void InsaneRebel2::decrementGaugeGroup(int slot, int targetId) {
+	_rebelValueCounters[slot]--;
+	if (_rebelValueCounters[slot] == 0) {
+		_rebelGaugeBlink[slot] = 6;
+		if (_rebelShieldGateActive && !_rebelReactorMode) {
+			_rebelShieldDestroyed = true;
+			debugC(DEBUG_INSANE, "Shield destroyed (gauge slot %d depleted by target %d)", slot, targetId);
+		}
+	}
+	_rebelLastCounter = _rebelValueCounters[slot];
 }
 
 void InsaneRebel2::resetExplosions() {
@@ -1023,6 +1031,7 @@ int InsaneRebel2::runLevel9() {
 		clearBit(0);
 		_rebelKillCounter = 0;
 		_rebelHitCounter = 0;
+		resetShieldGauge();
 
 		_rebelPhaseState = 0xfffffffe;
 


Commit: 1e934bfa1c5b6ff4e6124c12a19e22c73d8bebc3
    https://github.com/scummvm/scummvm/commit/1e934bfa1c5b6ff4e6124c12a19e22c73d8bebc3
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:37+02:00

Commit Message:
SCUMM: RA2: fidelity fixes for L10 and L15

Changed paths:
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/render.cpp


diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index 935d70e7a75..d02a717f90f 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -1285,7 +1285,7 @@ InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
 	//   Lv1->0, Lv2->1, Lv3->2, Lv4->3, Lv5->4,
 	//   Lv6A->5, Lv6B->6, Lv7->7 ... Lv14->14, Lv15A->15, Lv15B->16.
 	// Our Level 6 phase flow sets _currentPhase to 1/2 accordingly.
-	// Level 15 phase switch to 16 is currently approximated by _currentPhase >= 2.
+	// Level 15 switches to type 0x10 at frame 0x21e (updateLevel15TypeSwitch).
 	if (_selectedLevel <= 0) {
 		lvIdx = CLIP((int)_rebelLevelType, 0, 16);
 	} else if (_selectedLevel <= 5) {
@@ -1295,7 +1295,7 @@ InsaneRebel2::LevelDifficultyParams InsaneRebel2::getDifficultyParams() const {
 	} else if (_selectedLevel <= 14) {
 		lvIdx = _selectedLevel;
 	} else { // _selectedLevel == 15
-		lvIdx = (_currentPhase >= 2) ? 16 : 15;
+		lvIdx = (_rebelLevelType >= 0x10) ? 16 : 15;
 	}
 
 	lvIdx = CLIP(lvIdx, 0, 16);
diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index efd13079ae1..081e3f207fa 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -640,6 +640,7 @@ public:
 	void checkHandler7BoundaryZones(uint16 &warningMask);
 	void renderHandler7WarningCues(byte *renderBitmap, int pitch, int width, int height, int32 curFrame, uint16 warningMask);
 	void updateLevel7Fork(int32 curFrame);
+	void updateLevel15TypeSwitch(int32 curFrame);
 
 	int16 _playerDamage;
 	int16 _playerShield;
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index c7c97513b07..42da57d39bc 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -629,8 +629,16 @@ void InsaneRebel2::spawnSpaceShot(int x, int y) {
 			playSfx(6, 127, 0);
 
 			_spaceShots[i].counter = getShotMaxDuration();
+			// Shots are stored in presentation-window space (the projection without
+			// the scroll/crop offset), like the original; spawning happens before the
+			// frame's presentation pass, so strip the offset the projection baked in
+			// and let renderSpaceLaserShots apply the current frame's one.
 			Common::Point projected = getHandler7ProjectedPoint();
 			Common::Point target = getHandler7ShotTargetPoint();
+			projected.x -= _viewX;
+			projected.y -= _viewY;
+			target.x -= _viewX;
+			target.y -= _viewY;
 			int tableIndex = CLIP<int>(_shipDirectionIndex, 0, 34);
 
 			_spaceShots[i].targetX = target.x;
@@ -2126,6 +2134,12 @@ void InsaneRebel2::updateLevel7Fork(int32 curFrame) {
 	}
 }
 
+void InsaneRebel2::updateLevel15TypeSwitch(int32 curFrame) {
+	// From frame 0x21e level 15 uses the type-0x10 difficulty column; runLevel15 resets to 0xf per attempt.
+	if (_selectedLevel == 15 && curFrame >= 0x21e)
+		_rebelLevelType = 0x10;
+}
+
 void InsaneRebel2::renderPostRenderMenuCursor(byte *renderBitmap, int pitch, int width, int height) {
 	static const byte kRa2MenuCursor[] = {
 		 0,  0,  1,  1,  1,  1,  1,
@@ -2407,6 +2421,7 @@ void InsaneRebel2::procPostRendering(byte *renderBitmap, int32 codecparam, int32
 	updatePostRenderScroll(width, height);
 	updatePostRenderDeath();
 	updateLevel7Fork(curFrame);
+	updateLevel15TypeSwitch(curFrame);
 
 	// End the looping attack-run segment once the shield/reactor is destroyed.
 	if (_rebelShieldGateActive) {
@@ -3980,6 +3995,13 @@ void InsaneRebel2::renderVehicleLaserShots(byte *renderBitmap, int pitch, int wi
 void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int width, int height) {
 	int16 maxDuration = getShotMaxDuration();
 
+	// Shots are stored in presentation-window space; convert with the offset the
+	// current frame's presentation actually consumed (drawLaserBeam subtracts it
+	// back in high-res, the low-res paths draw into the scrolled/warped buffer).
+	const bool renderHiRes = isHiRes() && width >= 640 && height >= 400;
+	const int16 nativeViewX = renderHiRes ? _hiResPresentationViewX : _viewX;
+	const int16 nativeViewY = renderHiRes ? _hiResPresentationViewY : _viewY;
+
 	for (int i = 0; i < 2; i++) {
 		if (_spaceShots[i].counter <= 0)
 			continue;
@@ -3987,12 +4009,12 @@ void InsaneRebel2::renderSpaceLaserShots(byte *renderBitmap, int pitch, int widt
 		int16 pan = ((_spaceShots[i].targetX - 160) * (2 - _spaceShots[i].counter)) / 2;
 		pan = CLIP<int16>(pan, -127, 127);
 
-		int16 targetX = _spaceShots[i].targetX;
-		int16 targetY = _spaceShots[i].targetY;
-		int16 leftGunX = _spaceShots[i].leftGunX;
-		int16 leftGunY = _spaceShots[i].leftGunY;
-		int16 rightGunX = _spaceShots[i].rightGunX;
-		int16 rightGunY = _spaceShots[i].rightGunY;
+		int16 targetX = _spaceShots[i].targetX + nativeViewX;
+		int16 targetY = _spaceShots[i].targetY + nativeViewY;
+		int16 leftGunX = _spaceShots[i].leftGunX + nativeViewX;
+		int16 leftGunY = _spaceShots[i].leftGunY + nativeViewY;
+		int16 rightGunX = _spaceShots[i].rightGunX + nativeViewX;
+		int16 rightGunY = _spaceShots[i].rightGunY + nativeViewY;
 		int16 progress = maxDuration - _spaceShots[i].counter;
 
 		drawLaserBeam(renderBitmap, pitch, width, height,


Commit: cbc1f4d7ede7be4e7a56ab00374c4424cf20d0b3
    https://github.com/scummvm/scummvm/commit/cbc1f4d7ede7be4e7a56ab00374c4424cf20d0b3
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:37+02:00

Commit Message:
SCUMM: RA2: implement timepoints in all the levels

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 081e3f207fa..dc2596d6bb3 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -456,7 +456,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 updateGameplayTimedTick(int32 curFrame);
 	void checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
 
 	void renderTurretHudOverlays(byte *renderBitmap, int pitch, int width, int height, int32 curFrame);
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 42da57d39bc..232db68f421 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -2290,15 +2290,22 @@ void InsaneRebel2::updateGameplayDamageEffects(byte *renderBitmap, int pitch, in
 	}
 }
 
-void InsaneRebel2::updateGameplayDamageRecovery(int32 curFrame) {
-	// timed score tick in the same slot and does not reduce damage.
-	if ((_rebelHandler != 0x26 && _rebelHandler != 8 && _rebelHandler != 7) ||
-			(curFrame & 0xf) != 0 || _playerDamage <= 0) {
+void InsaneRebel2::updateGameplayTimedTick(int32 curFrame) {
+	// Every 16th frame the handlers score timePoints and recover one point of
+	// damage; the cover handler (25) scores without recovering.
+	if ((curFrame & 0xf) != 0)
 		return;
-	}
+	if (_rebelHandler != 7 && _rebelHandler != 8 && _rebelHandler != 25 && _rebelHandler != 0x26)
+		return;
+
+	const int16 timePoints = getDifficultyParams().timePoints;
+	if (timePoints > 0)
+		addScore(timePoints);
 
-	_playerDamage--;
-	_playerShield = 255 - _playerDamage;
+	if (_rebelHandler != 25 && _playerDamage > 0) {
+		_playerDamage--;
+		_playerShield = 255 - _playerDamage;
+	}
 }
 
 void InsaneRebel2::checkGameplayPostRenderCollisions(byte *renderBitmap, int pitch, int width, int height, int32 curFrame) {
@@ -2389,7 +2396,7 @@ void InsaneRebel2::renderGameplayPostFrame(byte *renderBitmap, int pitch, int wi
 
 	updateGameplayDamageEffects(renderBitmap, pitch, width, height);
 	checkGameplayPostRenderCollisions(renderBitmap, pitch, width, height, curFrame);
-	updateGameplayDamageRecovery(curFrame);
+	updateGameplayTimedTick(curFrame);
 
 	renderCrosshair(renderBitmap, pitch, width, height);
 


Commit: ceb75188de10119772d76a738526af8ad6a463a2
    https://github.com/scummvm/scummvm/commit/ceb75188de10119772d76a738526af8ad6a463a2
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:37+02:00

Commit Message:
SCUMM: RA2: correctly implemented waves in L2 and L12

Changed paths:
    engines/scumm/insane/rebel2/rebel.h
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/rebel.h b/engines/scumm/insane/rebel2/rebel.h
index dc2596d6bb3..edd517494b4 100644
--- a/engines/scumm/insane/rebel2/rebel.h
+++ b/engines/scumm/insane/rebel2/rebel.h
@@ -877,6 +877,7 @@ public:
 	static const LevelDifficultyParams kDifficultyTable[6][17];
 
 	LevelDifficultyParams getDifficultyParams() const;
+	int16 getWaveBudgetBase(int phase) const;
 
 	void addScore(int points);
 	void renderScoreHUD(byte *renderBitmap, int pitch, int width, int height, int statusBarY);
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index d9796d6bf2c..1517a5a9b5f 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -316,6 +316,23 @@ void InsaneRebel2::decrementGaugeGroup(int slot, int targetId) {
 	_rebelLastCounter = _rebelValueCounters[slot];
 }
 
+int16 InsaneRebel2::getWaveBudgetBase(int phase) const {
+	// Per-phase wave budgets share the tail columns (rollRate..driftRate) of the
+	// previous level-type row with the flight movement rates.
+	const LevelDifficultyParams &p =
+		kDifficultyTable[CLIP(_difficulty, 0, 5)][CLIP(_rebelLevelType - 1, 0, 16)];
+	switch (phase) {
+	case 1:
+		return p.rollRate;
+	case 2:
+		return p.liftRate;
+	case 3:
+		return p.slideRate;
+	default:
+		return p.driftRate;
+	}
+}
+
 void InsaneRebel2::resetExplosions() {
 	for (uint i = 0; i < ARRAYSIZE(_explosions); ++i) {
 		_explosions[i].active = false;
@@ -370,8 +387,6 @@ void InsaneRebel2::resetHandler7FlightState() {
 }
 
 int InsaneRebel2::runLevel2() {
-	const int16 kLevel2BudgetBase[3] = { 3, 3, 3 };
-
 	int bonusCount = 0;
 	int totalKills = 0;
 	int totalMisses = 0;
@@ -395,7 +410,7 @@ int InsaneRebel2::runLevel2() {
 
 		resetLevelPhaseState(false);
 
-		int16 budget = kLevel2BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
+		int16 budget = getWaveBudgetBase(1) + _vm->_rnd.getRandomNumber(2);
 
 		debugC(DEBUG_INSANE, "Level 2 Phase 1 - playing 02P01_A.SAN (background) budget=%d", budget);
 		if (!playLevelSegment("LEV02/P1/02P01_A.SAN", 0x28))
@@ -444,7 +459,7 @@ int InsaneRebel2::runLevel2() {
 		_currentPhase = 2;
 		resetLevelPhaseState(true);
 
-		budget = kLevel2BudgetBase[1] + _vm->_rnd.getRandomNumber(2);
+		budget = getWaveBudgetBase(2) + _vm->_rnd.getRandomNumber(2);
 
 		_rebelHandler = 8;
 
@@ -510,7 +525,7 @@ int InsaneRebel2::runLevel2() {
 		resetLevelPhaseState(true);
 		prevWaveState = 0;
 
-		budget = kLevel2BudgetBase[2] + _vm->_rnd.getRandomNumber(2);
+		budget = getWaveBudgetBase(3) + _vm->_rnd.getRandomNumber(2);
 
 		_rebelHandler = 8;
 
@@ -1120,8 +1135,6 @@ int InsaneRebel2::runLevel11() {
 	int totalMisses = 0;
 	int prevPhaseState = 0;
 
-	const int16 kLevel11BudgetBase[4] = { 3, 3, 3, 3 };
-
 	playCinematic("LEV11/11CUT.SAN");
 	if (_vm->shouldQuit())
 		return kLevelQuit;
@@ -1140,7 +1153,7 @@ int InsaneRebel2::runLevel11() {
 
 		resetLevelPhaseState(false);
 
-		int16 budget = kLevel11BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
+		int16 budget = getWaveBudgetBase(1) + _vm->_rnd.getRandomNumber(2);
 
 		debugC(DEBUG_INSANE, "Level 11 Phase 1 - playing 11P01_A.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV11/P1/11P01_A.SAN", 0x28))
@@ -1196,7 +1209,7 @@ int InsaneRebel2::runLevel11() {
 		_currentPhase = 2;
 		resetLevelPhaseState(true);
 
-		budget = kLevel11BudgetBase[1] + _vm->_rnd.getRandomNumber(2);
+		budget = getWaveBudgetBase(2) + _vm->_rnd.getRandomNumber(2);
 		_rebelHandler = 8;
 
 		debugC(DEBUG_INSANE, "Level 11 Phase 2 - playing 11P02_A.SAN budget=%d", budget);
@@ -1248,7 +1261,7 @@ int InsaneRebel2::runLevel11() {
 		resetLevelPhaseState(true);
 		prevPhaseState = 0;
 
-		budget = kLevel11BudgetBase[2] + _vm->_rnd.getRandomNumber(2);
+		budget = getWaveBudgetBase(3) + _vm->_rnd.getRandomNumber(2);
 		_rebelHandler = 8;
 
 		debugC(DEBUG_INSANE, "Level 11 Phase 3 first half - playing 11P03_A.SAN budget=%d", budget);
@@ -1323,7 +1336,7 @@ int InsaneRebel2::runLevel11() {
 
 		_rebelHandler = 8;
 
-		budget = kLevel11BudgetBase[3] + _vm->_rnd.getRandomNumber(2);
+		// The phase-3 budget carries into the second half; no new roll.
 
 		debugC(DEBUG_INSANE, "Level 11 Phase 3 second half - playing 11P03_G.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV11/P3/11P03_G.SAN", 0x28))
@@ -1392,8 +1405,6 @@ int InsaneRebel2::runLevel11() {
 }
 
 int InsaneRebel2::runLevel12() {
-	const int16 kLevel12BudgetBase[4] = { 3, 4, 4, 4 };
-
 	playCinematic("LEV12/12CUT.SAN");
 	if (_vm->shouldQuit())
 		return kLevelQuit;
@@ -1409,7 +1420,7 @@ int InsaneRebel2::runLevel12() {
 
 		resetLevelPhaseState(false);
 
-		int16 budget = kLevel12BudgetBase[0] + _vm->_rnd.getRandomNumber(2);
+		int16 budget = getWaveBudgetBase(1) + _vm->_rnd.getRandomNumber(2);
 
 		debugC(DEBUG_INSANE, "Level 12 Phase 1 - init 12P05.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV12/12P05.SAN", 0x28, false))
@@ -1456,7 +1467,7 @@ int InsaneRebel2::runLevel12() {
 		_currentPhase = 2;
 		resetLevelWaveState();
 
-		budget = kLevel12BudgetBase[1] + _vm->_rnd.getRandomNumber(3);
+		budget = getWaveBudgetBase(2) + _vm->_rnd.getRandomNumber(3);
 
 		debugC(DEBUG_INSANE, "Level 12 Phase 2 - init 12P06.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV12/12P06.SAN", 0x428, false))
@@ -1513,7 +1524,7 @@ int InsaneRebel2::runLevel12() {
 		_currentPhase = 3;
 		resetLevelWaveState();
 
-		budget = kLevel12BudgetBase[2] + _vm->_rnd.getRandomNumber(3);
+		budget = getWaveBudgetBase(3) + _vm->_rnd.getRandomNumber(3);
 
 		debugC(DEBUG_INSANE, "Level 12 Phase 3 - init 12P07.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV12/12P07.SAN", 0x428, false))
@@ -1567,7 +1578,7 @@ int InsaneRebel2::runLevel12() {
 		_currentPhase = 4;
 		resetLevelWaveState();
 
-		budget = kLevel12BudgetBase[3] + _vm->_rnd.getRandomNumber(3);
+		budget = getWaveBudgetBase(4) + _vm->_rnd.getRandomNumber(3);
 
 		debugC(DEBUG_INSANE, "Level 12 Phase 4 - init 12P08.SAN budget=%d", budget);
 		if (!playLevelSegment("LEV12/12P08.SAN", 0x428, false))


Commit: c14a871dd6b241e0048aeeef8f94015defe8468a
    https://github.com/scummvm/scummvm/commit/c14a871dd6b241e0048aeeef8f94015defe8468a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-07-02T08:36:37+02:00

Commit Message:
SCUMM: RA2: added missing gameplay sounds

Changed paths:
    engines/scumm/insane/rebel2/menu.cpp
    engines/scumm/insane/rebel2/rebel.cpp
    engines/scumm/insane/rebel2/render.cpp
    engines/scumm/insane/rebel2/runlevels.cpp


diff --git a/engines/scumm/insane/rebel2/menu.cpp b/engines/scumm/insane/rebel2/menu.cpp
index 555441cdfec..cc1ef27966b 100644
--- a/engines/scumm/insane/rebel2/menu.cpp
+++ b/engines/scumm/insane/rebel2/menu.cpp
@@ -663,6 +663,7 @@ int InsaneRebel2::runChapterSelect() {
 				}
 			}
 			_passwordInput.clear();
+			playSfx(3, 127, 0);
 			debugC(DEBUG_INSANE, "Password rejected for chapter %d", _chapterSelection + 1);
 		}
 	}
diff --git a/engines/scumm/insane/rebel2/rebel.cpp b/engines/scumm/insane/rebel2/rebel.cpp
index d02a717f90f..aec354ca8de 100644
--- a/engines/scumm/insane/rebel2/rebel.cpp
+++ b/engines/scumm/insane/rebel2/rebel.cpp
@@ -1326,6 +1326,7 @@ void InsaneRebel2::addScore(int points) {
 		int newMilestone = (_playerScore + points) / threshold;
 		if (oldMilestone < newMilestone) {
 			_playerLives++;
+			playSfx(5, 127, 0);
 			debugC(DEBUG_INSANE, "BONUS LIFE! Score crossed %d threshold. Lives=%d", threshold, _playerLives);
 		}
 	}
diff --git a/engines/scumm/insane/rebel2/render.cpp b/engines/scumm/insane/rebel2/render.cpp
index 232db68f421..23a73c2c0a7 100644
--- a/engines/scumm/insane/rebel2/render.cpp
+++ b/engines/scumm/insane/rebel2/render.cpp
@@ -1587,6 +1587,7 @@ void InsaneRebel2::checkCollisionZones(byte *renderBitmap, int pitch, int width,
 				}
 				if (!_noDamage)
 					initDamageFlash();
+				playSfx(1, 127, CLIP(((cx1 + cx2 + cx3 + cx4) / 4) * -4, -127, 127));
 			} else {
 				if (dparams.dodgePoints > 0) {
 					addScore(dparams.dodgePoints);
@@ -2976,6 +2977,23 @@ void InsaneRebel2::renderStatusBarSprites(byte *renderBitmap, int pitch, int wid
 	int numSprites = _smush_cockpitNut->getNumChars();
 	const int statusScale = isHiRes() ? 2 : getRebel2IndicatorScale(width, height);
 
+	// Critical-damage warning beep every 25 gameplay frames; volume depends on the level type.
+	if (_rebelHandler != 0 && _playerDamage > 170 && (curFrame % 25) == 0) {
+		int alertVolume;
+		switch (_rebelLevelType) {
+		case 1: case 11: case 12:
+			alertVolume = 40;
+			break;
+		case 2: case 3: case 5: case 6: case 7: case 9: case 10: case 13:
+			alertVolume = 63;
+			break;
+		default:
+			alertVolume = 127;
+			break;
+		}
+		playSfx(3, alertVolume, 0);
+	}
+
 	if (numSprites > 1) {
 		renderNutSprite(renderBitmap, pitch, width, height,
 			_viewX, statusBarY + _viewY, _smush_cockpitNut, 1);
@@ -4199,6 +4217,9 @@ void InsaneRebel2::renderCrosshair(byte *renderBitmap, int pitch, int width, int
 	}
 
 	if (targetLocked) {
+		// A fresh lock (timer fully decayed) pings LOCKON, panned by crosshair X.
+		if (_rebelHandler == 0x26 && _targetLockTimer == 0 && (getDifficultyParams().flags & 2) == 0)
+			playSfx(4, 127, CLIP(aimPos.x - 160, -127, 127));
 		_targetLockTimer = 7;
 	} else if (_targetLockTimer > 0) {
 		_targetLockTimer--;
diff --git a/engines/scumm/insane/rebel2/runlevels.cpp b/engines/scumm/insane/rebel2/runlevels.cpp
index 1517a5a9b5f..3789770b50a 100644
--- a/engines/scumm/insane/rebel2/runlevels.cpp
+++ b/engines/scumm/insane/rebel2/runlevels.cpp
@@ -1169,6 +1169,7 @@ int InsaneRebel2::runLevel11() {
 				uint16 waveSelect = waveEnd.creditedBits;
 
 				if ((_rebelPhaseState & 0x10) != 0 && (prevPhaseState & 0x10) == 0) {
+					playAuxSfx(3, 127, 0);
 				}
 				prevPhaseState = _rebelPhaseState;
 
@@ -1277,6 +1278,7 @@ int InsaneRebel2::runLevel11() {
 					return kLevelQuit;
 
 				if ((_rebelPhaseState & 0x0e) == 0x0e && (prevPhaseState & 0x0e) != 0x0e) {
+					playAuxSfx(3, 127, 0);
 				}
 				prevPhaseState = _rebelPhaseState;
 




More information about the Scummvm-git-logs mailing list